.NET 6 Linq 的新功能来到最终回合。
本集提要
- 框架 : .NET 6
- 功能 : DistinctBy、ExceptBy、IntersectBy 和.UnionBy
方法宣告
在 .NET6 中,这四个方法分别有两个多载:
DistinctBy
- IEnumerable<TSource> DistinctBy<TSource,TKey> (this IEnumerable<TSource> source, Func<TSource,TKey> keySelector)
- IEnumerable<TSource> DistinctBy<TSource,TKey> (this IEnumerable<TSource> source, Func<TSource,TKey> keySelector, IEqualityComparer<TKey>? comparer)
ExceptBy
- IEnumerable<TSource> ExceptBy<TSource,TKey> (this IEnumerable<TSource> first, IEnumerable<TKey> second, Func<TSource,TKey> keySelector)
- IEnumerable<TSource> ExceptBy<TSource,TKey> (this IEnumerable<TSource> first, IEnumerable<TKey> second, Func<TSource,TKey> keySelector, IEqualityComparer<TKey>? comparer)
IntersectBy
- IEnumerable<TSource> IntersectBy<TSource,TKey> (this IEnumerable<TSource> first, IEnumerable<TKey> second, Func<TSource,TKey> keySelector);
- IEnumerable<TSource> IntersectBy<TSource,TKey> (this IEnumerable<TSource> first, IEnumerable<TKey> second, Func<TSource,TKey> keySelector, IEqualityComparer<TKey>? comparer)
UnionBy
- IEnumerable<TSource> UnionBy<TSource,TKey> (this IEnumerable<TSource> first, IEnumerable<TSource> second, Func<TSource,TKey> keySelector)
- 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