找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

3236

积分

0

好友

420

主题
发表于 昨天 09:36 | 查看: 1| 回复: 0

在 C# 开发中,对集合进行高效的关系运算,比如去重、合并、查找共同项或差异项,是数据处理的基本功。LINQ 提供了一组强大的集合关系操作方法,其中 DistinctUnionIntersectExcept 是最核心的四个。本文将深入剖析它们的用法、原理,并结合 .NET 6 引入的新方法,通过一个模拟 MVVM 数据处理场景的实战练习,帮助你彻底掌握。

一、Distinct(去重)

功能

  • 移除序列中的重复元素,返回一个由唯一元素构成的新集合。
  • 默认使用元素类型的 EqualsGetHashCode 方法来判定唯一性。
  • 可通过传递一个 IEqualityComparer<T> 比较器来自定义去重规则。

延迟执行

  • 与其他大多数 LINQ 算子一样,Distinct 采用延迟执行策略,即不枚举则不执行
  • 只有在实际遍历结果序列(例如使用 foreach)时,去重操作才会发生。

基础示例

var names = new List<string> { “Alice”, “Bob”, “Alice”, “Charlie” };

var uniqueNames = names.Distinct();

foreach (var name in uniqueNames)
{
    Console.WriteLine(name);
}
// 输出顺序:Alice, Bob, Charlie

思考与实践:如何自定义去重规则?

  1. Distinct 对引用类型的默认判定依据是什么?
    Distinct() 默认使用对象的 GetHashCode()Equals() 进行比较。这意味着对于自定义类,你需要正确重写这两个方法,否则会根据引用地址判断,可能达不到预期效果。

  2. 如果希望按自定义条件(如 Student.Name)去重,怎么做?

方法一:使用内置的 StringComparer(最推荐:处理字符串时)
如果你只是想处理字符串的大小写问题,不需要自己写类,直接传入 .NET 自带的比较器即可。

var names = new List<string> { “Alice”, “alice”, “Bob” };

// 忽略大小写去重
var uniqueNames = names.Distinct(StringComparer.OrdinalIgnoreCase);

foreach (var name in uniqueNames)
 {
     Console.WriteLine(name); // 输出:Alice, Bob
 }

方法二:实现 IEqualityComparer<T> 接口(最标准:适用于复杂逻辑)
如果你有更特殊的规则(比如:只要首字母相同就视为同一个名字),你需要创建一个自定义的比较器类。理解并实现这个接口,是处理复杂对象比较的关键,也是很多算法和数据处理逻辑的基础。

public class FirstLetterComparer : IEqualityComparer<string>
 {
// 判断两个对象是否相等
public bool Equals(string x, string y)
     {
if (x == null || y == null) return false;
return x[0] == y[0]; // 规则:首字母相同即相等
     }

// 计算哈希值
// 注意:Equals 返回 true 的两个对象,GetHashCode 必须也返回相同的值
public int GetHashCode(string obj)
     {
return obj == null ? 0 : obj[0].GetHashCode();
     }
 }

// 使用方式:
var uniqueNames = names.Distinct(new FirstLetterComparer());

方法三:使用 LINQ 的 DistinctBy(.NET 6+ 最便捷)
如果在处理对象列表(例如 List<User>),且只想根据对象的某个字段去重,DistinctBy 是性能最高且最简洁的选择。

var users = new List<User> 
 {
new User { Id = 1, Name = “Alice” },
new User { Id = 2, Name = “Alice” }, // Name 重复
new User { Id = 3, Name = “Bob” }
 };

// 根据 Name 属性去重,保留遇到的第一个对象
var uniqueUsers = users.DistinctBy(u => u.Name);

foreach (var user in uniqueUsers)
 {
     Console.WriteLine(user.Id + “: “ + user.Name); 
 }
// 输出:
// 1: Alice
// 3: Bob

二、Union(并集)

功能

  • 合并两个序列,并自动去除重复项。
  • 返回的是两个序列中所有唯一元素组成的新序列。

延迟执行

  • 同样是延迟执行,遍历时才会计算并集。
  • 可使用默认比较规则或通过 IEqualityComparer<T> 指定自定义比较器。

基础示例

var listA = new List<string> { “Alice”, “Bob” };
var listB = new List<string> { “Bob”, “Charlie” };

var union = listA.Union(listB);

foreach (var name in union)
{
    Console.WriteLine(name);
}
// 输出顺序:Alice, Bob, Charlie

思考与实践:如何自定义合并规则?

  1. Union 是否改变原集合?
    不改变,它返回的是一个全新的序列。

  2. 如何让 Union 使用自定义比较规则?

1. 简单字符串:忽略大小写合并
这是最常见的需求。如果你希望 “Alice” 和 “alice” 被视为同一个元素,只需传入 StringComparer

var listA = new List<string> { “Alice”, “Bob” };
var listB = new List<string> { “alice”, “Charlie” };

// 使用内置比较器
var union = listA.Union(listB, StringComparer.OrdinalIgnoreCase);

foreach (var name in union)
  {
      Console.WriteLine(name); 
  }
// 输出:Alice, Bob, Charlie (注意:保留的是第一次遇到的 “Alice”)

2. 复杂对象:根据特定属性合并 (UnionBy)
.NET 6及更高版本 中,如果你有两个对象列表(比如从两个不同的数据库查出来的用户信息),想根据 IdName 合并,UnionBy 是最高效的选择。

var listA = new List<User> { new User(1, “Alice”), new User(2, “Bob”) };
var listB = new List<User> { new User(2, “Bob”), new User(3, “Charlie”) };

// 根据 User 的 Id 属性进行合并
var union = listA.UnionBy(listB, u => u.Id);

// 结果将包含 Id 为 1, 2, 3 的三个 User 对象

3. 高级自定义:实现 IEqualityComparer<T>
如果你需要更复杂的合并逻辑(例如:名字长度相同且首字母相同才算重复),你需要像 Distinct 那样实现一个自定义比较器。

public class MyCustomComparer : IEqualityComparer<string>
  {
public bool Equals(string x, string y)
      {
// 自定义逻辑:长度相同即视为重复
return x?.Length == y?.Length;
      }

public int GetHashCode(string obj)
      {
return obj?.Length.GetHashCode() ?? 0;
      }
  }

// 使用
var union = listA.Union(listB, new MyCustomComparer());

三、Intersect(交集)

功能

  • 返回两个序列中都存在的元素(即共同项)。
  • 默认使用 Equals/GetHashCode 判定元素是否相同。

示例

var listA = new List<string> { “Alice”, “Bob” };
var listB = new List<string> { “Bob”, “Charlie” };

var intersection = listA.Intersect(listB);

foreach (var name in intersection)
{
    Console.WriteLine(name);
}
// 输出:Bob

四、Except(差集)

功能

  • 返回序列 A 中存在但序列 B 中不存在的元素。

示例

var listA = new List<string> { “Alice”, “Bob”, “Charlie” };
var listB = new List<string> { “Bob” };

var except = listA.Except(listB);

foreach (var name in except)
{
    Console.WriteLine(name);
}
// 输出:Alice, Charlie

五、实战编码练习(模拟 MVVM 数据处理)

场景:班级选课数据处理。我们需要对两个班级的选课学生名单进行各种关系运算。

首先,定义数据模型:

class Student
{
public string Name { get; set; }
public string Course { get; set; }
}

初始化数据:

var studentsA = new List<Student>
{
new Student { Name=“Alice”, Course=“Math” },
new Student { Name=“Bob”, Course=“English” },
new Student { Name=“Alice”, Course=“Math” } // 重复项
};

var studentsB = new List<Student>
{
new Student { Name=“Charlie”, Course=“Math” },
new Student { Name=“Bob”, Course=“English” }
};

现在,开始进行一系列集合操作

// 1. 班级A学生去重(按 Name + Course 组合键)
var uniqueA = studentsA.DistinctBy(s => (s.Name, s.Course));

// 2. A 和 B 的学生并集(按 Name + Course 去重)
// 使用 EqualityComparer.Create 快速创建临时比较器,无需单独实现类
var unionAB = uniqueA.Union(studentsB, EqualityComparer<Student>.Create((x, y) => x.Name == y.Name && x.Course == y.Course, s => HashCode.Combine(s.Name, s.Course)));

// 3. 交集:A 和 B 都选课的学生(同名同课程)
var intersectAB = uniqueA.Intersect(studentsB, EqualityComparer<Student>.Create((x, y) => x.Name == y.Name && x.Course == y.Course, s => HashCode.Combine(s.Name, s.Course)));

// 4. 差集:只在A班选课,B班没有的学生
var exceptAB = uniqueA.Except(studentsB, EqualityComparer<Student>.Create((x, y) => x.Name == y.Name && x.Course == y.Course, s => HashCode.Combine(s.Name, s.Course)));

技术要点:这个练习综合运用了 .NET 6+DistinctBy 和通过 EqualityComparer.Create 动态创建自定义比较器的方法,模拟了实际业务开发中常见的“按特定字段组合进行去重、合并与比较”的复杂场景。熟练掌握这些方法,能极大提升数据处理代码的简洁性和效率。

通过本文对 Distinct, Union, Intersect, Except 的详细拆解和实战演练,你应该已经掌握了 LINQ 集合关系操作的核心。理解其延迟执行特性和自定义比较规则的方法,是灵活运用的关键。在实际开发中,结合 .NET 6 及更高版本的新 *By 系列方法,能让你的代码更加优雅高效。如果你想深入探讨更多 C#LINQ 的高级用法,欢迎到 云栈社区 的技术论坛板块,与更多开发者交流学习心得和实战经验。更多系统性的技术文档和教程也可以在社区的知识库中找到。




上一篇:FSCAN免杀实战:从源码混淆到加壳的完整操作指南
下一篇:wechat-article-exporter:微信公众号文章批量下载工具,支持多格式导出与Docker部署
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-2-9 00:52 , Processed in 0.300102 second(s), 39 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表