Linq 新功能 (5) DistinctBy、ExceptBy、IntersectBy 和 UnionBy

.NET 6 Linq 的新功能来到最终回合。

本集提要
  • 框架 : .NET 6
  • 功能 : DistinctBy、ExceptBy、IntersectBy 和.UnionBy
方法宣告

在 .NET6 中,这四个方法分别有两个多载:

DistinctBy
  1. IEnumerable<TSource> DistinctBy<TSource,TKey> (this IEnumerable<TSource> source, Func<TSource,TKey> keySelector)
  2. IEnumerable<TSource> DistinctBy<TSource,TKey> (this IEnumerable<TSource> source, Func<TSource,TKey> keySelector, IEqualityComparer<TKey>? comparer)
ExceptBy
  1. IEnumerable<TSource> ExceptBy<TSource,TKey> (this IEnumerable<TSource> first, IEnumerable<TKey> second, Func<TSource,TKey> keySelector)
  2. IEnumerable<TSource> ExceptBy<TSource,TKey> (this IEnumerable<TSource> first, IEnumerable<TKey> second, Func<TSource,TKey> keySelector, IEqualityComparer<TKey>? comparer)
IntersectBy
  1. IEnumerable<TSource> IntersectBy<TSource,TKey> (this IEnumerable<TSource> first, IEnumerable<TKey> second, Func<TSource,TKey> keySelector);
  2. IEnumerable<TSource> IntersectBy<TSource,TKey> (this IEnumerable<TSource> first, IEnumerable<TKey> second, Func<TSource,TKey> keySelector, IEqualityComparer<TKey>? comparer)
UnionBy
  1. IEnumerable<TSource> UnionBy<TSource,TKey> (this IEnumerable<TSource> first, IEnumerable<TSource> second, Func<TSource,TKey> keySelector)
  2. IEnumerable<TSource> UnionBy<TSource,TKey> (this IEnumerable<TSource> first, IEnumerable<TSource> second, Func<TSource,TKey> keySelector, IEqualityComparer<TKey>? comparer);
Distinct vs DistinctBy

我们先瞧瞧 Distinct 替代关係,就 DistinctBy<TSource,TKey>(IEnumerable<TSource>, Func<TSource,TKey>) 这个多载而言,很明显在替代 Distinct 中带有 IEqualityComparer<TSource> 的多载,举个简单的例子,如果我有以下字串阵列:

 string[] colors = { "red", "green", "blue", "green", "yellow", "blue" };

如果想要取得不重複的字串序列,你应该会这么用:

var distinctColors = colors.Distinct();

.NET 6 以后你也可以这么用:

 var distinctColors = colors.DistinctBy(c => c);

明显地脱裤子放屁,这原因在于 Distinct<TSource>(IEnumerable<TSource>) 会使用元素的相等比较,所以用字串自己的相等比较就可以决定『独特的字串』(注1),所以在此等情境下,直接用原来的 Distinct 就可以了。

另外一个例子就会凸显出 DistinctBy 是比较容易撰写的,假设我们要独一化的条件是『字串长度』,在这个需求下如果用 Distinct 就得先实作一个  EqualityComparer:

 public class StringLengthEqualityComparer : IEqualityComparer<string>
 {
     public bool Equals(string x, string y)
     {
         if (x == y) { return true; }
         if (x == null || y == null) { return false; }
         return x.Length == y.Length;
     }
     public int GetHashCode(string obj)
     {
         if (obj == null) { return -1; }
         return obj.Length;
     }
 }

然后这么使用它:

var distinctColorsByLength = colors.Distinct(new StringLengthEqualityComparer());

但如果用 DistinctBy 可以直接写,无须另外新增类别:

distinctColorsByLength = colors.DistinctBy(c => c.Length);

注1: 我说的笼统了一些,事实上原始码内部要取得实作的 EqualityComparer 另外牵扯到要不要区分大小写的问题,还挺啰嗦一把的。

Union vs UnionBy

UnionBy 的替代形式近似于 DistinctBy,假设有以下两个阵列,Union (联集) 的条件是字串的长度:

 string[] colors = { "red", "green", "blue", "green", "yellow", "pink" };
 string[] insects = { "ant", "bee", "beetle", "fly",  "butterfly", "grasshopper" };

用 Union 会这样写,和前面的 Distinct 一样,必须先建立一个 EqualityComparer:

 public class StringLengthEqualityComparer : IEqualityComparer<string>
 {
     public bool Equals(string x, string y)
     {
         if (x == y) { return true; }
         if (x == null || y == null) { return false; }
         return x.Length == y.Length;
     }
     public int GetHashCode(string obj)
     {
         if (obj == null) { return -1; }
         return obj.Length;
     }
 }
 var result = colors.Union(insects, new StringLengthEqualityComparer());

这个情况使用 UnionBy 就简单多了:

var result = colors.UnionBy(insects, c => c.Length);
ExceptBy 与 IntersectBy

这两个会一起谈是有原因的,因为 ExceptBy 和 IntersectBy 的使用情境和 UnionBy 是不一样的。

UnionBy 还是两个同元素的集合取得联集,瞧瞧 UnionBy 的前两个参数都是 IEnumerable<TSource>,但 ExceptBy 和 IntersectBy 第一个参数是 IEnumerable<TSource> ,第二个却是 IEnumerable<TKey>,而原来的 Union/Except/Intersect 方法前两个参数都是 IEnumerable<TSource>。

IEnumerable<TSource> 代表的是来源序列,这没问题;至于第二个 IEnumerable<TKey> 则是作为 Except 或 Intersect 条件的键值,这样说有点模糊,我们来举个例子。

假设有一个 Student 阵列里有七个元素:

 static Student[] CreateStudents()
 {
     new Student { Id = 1, Name = "Bill" },
     new Student { Id = 2, Name = "Steve" },
     new Student { Id = 3, Name = "Ram" },
     new Student { Id = 4, Name = "Alex" },
     new Student { Id = 5, Name = "Mary" },
     new Student { Id = 6, Name = "David" },
     new Student { Id = 7, Name = "Steve" }
 };

 我们想要以 Id 值做 Except 或 Intersect,所以来一个 int 阵列:

 int[] ids = { 1, 4, 6 };

于是我们就可以这样搞:

 static void Main(string[] args)
 {
     int[] ids = { 1, 4, 6 };
     Student[] students = CreateStudents();
     var result = students.ExceptBy(ids, s => s.Id);
     Display(result);
     Console.WriteLine("--------------");
     result = students.IntersectBy(ids, s => s.Id);
     Display(result);

 }
範例

本次的範例比较分散:

  • DistinctBy Sample
  • UnionBy Sample
  • ExceptBy and IntersectBy Sample
参考资料

Microsoft Learn .NET6 新的 LINQ API

关于作者: 网站小编

码农网专注IT技术教程资源分享平台,学习资源下载网站,58码农网包含计算机技术、网站程序源码下载、编程技术论坛、互联网资源下载等产品服务,提供原创、优质、完整内容的专业码农交流分享平台。

热门文章

5 点赞(415) 阅读(67)