在C#开发中,经常需要对两个集合进行比对,找出共同部分或独有部分。LINQ提供的Intersect与Except方法可以用极少的代码完成这类集合运算,而不必手写嵌套循环。理解它们的执行逻辑和比较规则,能避免很多隐藏的数据错误。

Intersect方法的基本使用
Intersect用于返回两个序列中均存在的元素集合。它位于System.Linq命名空间,是IEnumerable<T>的扩展方法。调用时,第一个集合调用Intersect并传入第二个集合,最终结果为交集。
下面的例子展示两个整数列表求交集的过程。Intersect内部会先将第二个序列装入哈希集,再遍历第一个序列做包含判断,因此平均时间复杂度约为O(n+m)。
using System;
using System.Collections.Generic;
using System.Linq;
class Program
{
static void Main()
{
List<int> listA = new List<int> { 1, 2, 3, 4, 5 };
List<int> listB = new List<int> { 4, 5, 6, 7 };
// 求交集,结果为 4,5
var common = listA.Intersect(listB);
foreach (var item in common)
{
Console.WriteLine(item);
}
}
}
上述代码输出4和5,顺序以第一个序列为准。需要注意的是,Intersect返回的是延迟执行结果,只有在遍历时才会真正计算。如果希望立即获得结果,可以加上ToList或ToArray。
对于字符串或值类型,Intersect使用默认相等比较器即可。但如果序列元素为自定义类,直接使用Intersect可能得不到预期结果,因为默认比较的是引用地址。
Except方法的基本使用
Except用于返回存在于第一个序列但不存在于第二个序列的元素,即差集。它与Intersect互补,常用于权限比对、数据同步等场景。
以下示例求出listA中有而listB中没有的元素:
using System;
using System.Collections.Generic;
using System.Linq;
class Program
{
static void Main()
{
List<int> listA = new List<int> { 1, 2, 3, 4, 5 };
List<int> listB = new List<int> { 4, 5, 6, 7 };
// 求差集,结果为 1,2,3
var onlyInA = listA.Except(listB);
foreach (var item in onlyInA)
{
Console.WriteLine(item);
}
}
}
运行后输出1、2、3。Except同样基于哈希集实现,且只保留第一个序列中独有的项,不会包含第二个序列的独有项。若需要对称差集,可结合两次Except并使用Concat。
延迟执行特性在Except中同样存在。若后续listA或listB被修改,再次遍历结果时会反映修改,因此建议在确定数据不变后再 materialize 结果。
自定义对象的比对处理
当集合元素为自定义类时,Intersect与Except默认使用引用相等,往往不符合业务预期。此时应重写Equals与GetHashCode,或传入IEqualityComparer<T>。
下面定义一个产品类,并通过比较器按编号求交集:
using System;
using System.Collections.Generic;
using System.Linq;
class Product
{
public int Id { get; set; }
public string Name { get; set; }
}
class ProductComparer : IEqualityComparer<Product>
{
public bool Equals(Product x, Product y)
{
if (ReferenceEquals(x, y)) return true;
if (x == null || y == null) return false;
return x.Id == y.Id;
}
public int GetHashCode(Product obj)
{
return obj.Id.GetHashCode();
}
}
class Program
{
static void Main()
{
var a = new List<Product>
{
new Product { Id = 1, Name = "笔" },
new Product { Id = 2, Name = "本" }
};
var b = new List<Product>
{
new Product { Id = 2, Name = "本子" },
new Product { Id = 3, Name = "尺" }
};
var same = a.Intersect(b, new ProductComparer());
foreach (var p in same)
{
Console.WriteLine(p.Name);
}
}
}
上述代码输出“本”,因为编号2同时存在于两个集合。使用比较器的方式比重写类方法更灵活,尤其当同一类在不同场景需要不同比对规则时。
如果不想额外定义类,也可先用Select提取关键属性组成匿名序列,再调用Intersect,最后用Join回查完整对象,这样能减少样板代码。
性能与注意事项
Intersect与Except在空间上会缓存第二个序列的全部元素到哈希集,因此当第二个集合极大时需注意内存占用。若只需判断存在性且数据可流式处理,可考虑用HashSet手动实现以精细控制。
两者都不会去除第一个序列内部的重复项,若原序列含重复元素,结果中对应项也只出现一次,这是集合语义决定的。如需保留重复次数语义,应使用GroupBy自行计算交集多重集。
| 方法 | 语义 | 空序列行为 |
|---|---|---|
| Intersect | 两序列共有 | 任一为空则结果为空 |
| Except | 前者有后者无 | 后者为空则返回前者去重 |
在实际项目中,推荐将结果立即ToList以减少重复计算,并针对大数据量做分批处理,避免一次性构建超大哈希集导致内存峰值过高。
C#LINQ_IntersectLINQ_Except修改时间:2026-08-05 10:15:22