linqin,何止 Linq 的 Distinct 不给力

昨日看到一篇文章 《Linq的Distinct太不给力了》,文中指出 Linq 中 Distinct 方法的一个重载使用了 IEqualityComparer 作为参数,调用时大多都要创建新的类去实现这个接口,很不给力。文中给出了一种解决办法,略显烦索,我也写了《c# 扩展方法 奇思妙用 基础篇 八:Distinct 扩展》一文使用扩展方法予以简化。
但问题远远没有结束,不给力是因为使用了 IEqualityComparer 作为参数,而 .net 中将 IEqualityComparer 用作参数的地方相当多:

IEqualityComparer 用作参数

.net 中 IEqualityComparer 用作参数,大致可分为以下两种情况:

1. Linq

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
public static class Enumerable { public static bool Contains(this IEnumerable source, TSource value, IEqualityComparer comparer); public static IEnumerable Distinct(this IEnumerable source, IEqualityComparer comparer); public static IEnumerable Except(this IEnumerable first, IEnumerable second, IEqualityComparer comparer); public static IEnumerable> GroupBy(this IEnumerable source, Func keySelector, IEqualityComparer comparer); public static IEnumerable Intersect(this IEnumerable first, IEnumerable second, IEqualityComparer comparer); public static bool SequenceEqual(this IEnumerable first, IEnumerable second, IEqualityComparer comparer); public static Dictionary ToDictionary(this IEnumerable source, Func keySelector, IEqualityComparer comparer); public static ILookup ToLookup(this IEnumerable source, Func keySelector, IEqualityComparer comparer); public static IEnumerable Union(this IEnumerable first, IEnumerable second, IEqualityComparer comparer); //... }
同样 Queryable 类中也有类似的一些方法

2. 字典、集合类

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
19 20
public class Dictionary : IDictionary, ICollection>,
IEnumerable>, IDictionary, ICollection, IEnumerable, ISerializable, IDeserializationCallback { public Dictionary(); public Dictionary(IDictionary dictionary); public Dictionary(IEqualityComparer comparer); public Dictionary(int capacity); public Dictionary(IDictionary dictionary, IEqualityComparer comparer); public Dictionary(int capacity, IEqualityComparer comparer); //... } public class HashSet : ISerializable, IDeserializationCallback, ISet, ICollection, IEnumerable, IEnumerable { public HashSet(); public HashSet(IEnumerable collection); public HashSet(IEqualityComparer comparer); public HashSet(IEnumerable collection, IEqualityComparer comparer); //... }
Dictionary 和 HashSet 类的构造函数都用到了 IEqualityComparer 接口。
除了如上两个,还有 ConcurrentDictionary、SortedSet、KeyedCollection(抽象类)、SynchronizedKeyedCollection 等等也使用 IEqualityComparer 接口作为构造函数的参数。
 
IEqualityComparer 作为参数多在复杂的重载中出现,满足一些特殊情况的要求,而相应的简单的重载确是经常使用的。因此,虽然 IEqualityComparer 在 .net 应用广泛,但在我们编程时,确是较少涉及。 不过话又说回来,一旦使用到时,就会感觉相当麻烦。多数时候你不得不去创建一个新类,去实现 IEqualityComparer 接口,再去 new 一个实例,而你真正需要的可能仅仅是根据某个属性(如 ID )进行比较。创建新类实现 IEqualityComparer 接口,不但增加了代码量,还增加的复杂度:你要考虑这个新类放在哪里合适,如何命名等等。
因此,我们期望有一个简单的方法来能直接创建 IEqualityComparer 的实例。《c# 扩展方法 奇思妙用 基础篇 八:Distinct 扩展》一文中给出了一个简单实用的类 CommonEqualityComparer,在这里可以复用来达到我们的目标。

CommonEqualityComparer

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
using System; using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Linq; public class CommonEqualityComparer : IEqualityComparer { private Func keySelector; private IEqualityComparer comparer; public CommonEqualityComparer(Func keySelector, IEqualityComparer comparer) { this.keySelector = keySelector; this.comparer = comparer; } public CommonEqualityComparer(Func keySelector) : this(keySelector, EqualityComparer.Default) { } public bool Equals(T x, T y) { return comparer.Equals(keySelector(x), keySelector(y)); } public int GetHashCode(T obj) { return comparer.GetHashCode(keySelector(obj)); } }
使用这个类,可以简易通过 lambda 表达式来创建 IEqualityComparer 的实例:
1 2 3 4 5 6
var dict = new Dictionarystring>(new CommonEqualityComparerstring>(p => p.Name)); List persons = null; Person p1 = null; //... var ps = persons.Contains(p1, new CommonEqualityComparerint>(p=>p.ID));
相信看了上面代码的,你会觉得 new CommonEqualityComparer(p => p.Name)) 太冗长。不过我们可以借助下面的类加以改善:
1 2 3 4 5 6 7 8 9 10 11
public static class Equality { public static IEqualityComparer CreateComparer(Func keySelector) { return new CommonEqualityComparer(keySelector); } public static IEqualityComparer CreateComparer(Func keySelector, IEqualityComparer comparer) { return new CommonEqualityComparer(keySelector, comparer); } }
调用代码可简化:
1 2
var dict = new Dictionarystring>(Equality.CreateComparer(p => p.Name)); var ps = persons.Contains(p1, Equality.CreateComparer(p => p.ID));
不考虑类名和方法名的前提下,Equality.CreateComparer(p => p.ID) 的写法也经精简到极限了(如果你能进一步精简,不妨告诉我)。
其实有了 Equality 这个类,我们大可将 CommonEqualityComparer 类封装隐藏起来。

Equality

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
public static class Equality { public static IEqualityComparer CreateComparer(Func keySelector) { return new CommonEqualityComparer(keySelector); } public static IEqualityComparer CreateComparer(Func keySelector, IEqualityComparer comparer) { return new CommonEqualityComparer(keySelector, comparer); } class CommonEqualityComparer : IEqualityComparer { private Func keySelector; private IEqualityComparer comparer; public CommonEqualityComparer(Func keySelector, IEqualityComparer comparer) { this.keySelector = keySelector; this.comparer = comparer; } public CommonEqualityComparer(Func keySelector) : this(keySelector, EqualityComparer.Default) { } public bool Equals(T x, T y) { return comparer.Equals(keySelector(x), keySelector(y)); } public int GetHashCode(T obj) { return comparer.GetHashCode(keySelector(obj)); } } }
CommonEqualityComparer 封装成了 Equaility 的嵌套类 CommonEqualityComparer,对外不可见,降低了使用的复杂度。
《c# 扩展方法 奇思妙用 基础篇 八:Distinct 扩展》一文中的 Distinct 扩展方法 写起来也简单了:
1 2 3 4 5 6 7 8 9 10 11
public static class DistinctExtensions { public static IEnumerable Distinct(this IEnumerable source, Func keySelector) { return source.Distinct(Equality.CreateComparer(keySelector)); } public static IEnumerable Distinct(this IEnumerable source, Func keySelector, IEqualityComparer comparer) { return source.Distinct(Equality.CreateComparer(keySelector, comparer)); } }
Linq 中除 Distinct 外还有众多方法使用了 IEqualityComparer 接口,逐一扩展未必是一个好方式,使用 Equality.CreateComparer 方法比较明智。

总结

.net 中经常把 IEqualityComparer 用作某些重载的参数,但这些重载在日常使用中并不频繁。 不过一旦用到,大多要创建新类实现 IEqualityComparer,繁琐不给力。 本文创建 Equality 泛型类,配合一个 lambda 表达式可快速创建 IEqualityComparer 的实例。
Tags:  linqin

延伸阅读

最新评论

发表评论