昨日看到一篇文章 《 Linq的Distinct太不給力了 》,文中指出 Linq 中 Distinct 方法的一個重載使用了 IEqualityComparer<T> 作為參數,調用時大多都要創建新的類去實現這個接口,很不給力。文中給出了一種解決辦法,略顯煩索,我也寫了《 c# 擴展方法 奇思妙用 基礎篇 八:Distinct 擴展 》一文使用 擴展方法 予以簡化。
但問題遠遠沒有結束,不給力是因為使用了 IEqualityComparer<T> 作為參數,而 .net 中將 IEqualityComparer<T> 用作參數的地方相當多:
IEqualityComparer<T> 用作參數
.net 中 IEqualityComparer<T> 用作參數,大致可分為以下兩種情況:
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<TSource>( this IEnumerable<TSource> source, TSource value , IEqualityComparer<TSource> comparer); public static IEnumerable<TSource> Distinct<TSource>( this IEnumerable<TSource> source, IEqualityComparer<TSource> comparer); public static IEnumerable<TSource> Except<TSource>( this IEnumerable<TSource> first, IEnumerable<TSource> second, IEqualityComparer<TSource> comparer); public static IEnumerable<IGrouping<TKey, TSource>> GroupBy<TSource, TKey>( this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, IEqualityComparer<TKey> comparer); public static IEnumerable<TSource> Intersect<TSource>( this IEnumerable<TSource> first, IEnumerable<TSource> second, IEqualityComparer<TSource> comparer); public static bool SequenceEqual<TSource>( this IEnumerable<TSource> first, IEnumerable<TSource> second, IEqualityComparer<TSource> comparer); public static Dictionary<TKey, TSource> ToDictionary<TSource, TKey>( this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, IEqualityComparer<TKey> comparer ); public static ILookup<TKey, TSource> ToLookup<TSource, TKey>( this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, IEqualityComparer<TKey> comparer); public static IEnumerable<TSource> Union<TSource>( this IEnumerable<TSource> first, IEnumerable<TSource> second, IEqualityComparer<TSource> 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<TKey, TValue> : IDictionary<TKey, TValue>, ICollection<KeyValuePair<TKey, TValue>>, IEnumerable<KeyValuePair<TKey, TValue>>, IDictionary, ICollection, IEnumerable, ISerializable, IDeserializationCallback { public Dictionary(); public Dictionary(IDictionary<TKey, TValue> dictionary); public Dictionary( IEqualityComparer<TKey> comparer); public Dictionary( int capacity); public Dictionary(IDictionary<TKey, TValue> dictionary, IEqualityComparer<TKey> comparer); public Dictionary( int capacity, IEqualityComparer<TKey> comparer); //... } public class HashSet<T> : ISerializable, IDeserializationCallback, ISet<T>, ICollection<T>, IEnumerable<T>, IEnumerable { public HashSet(); public HashSet(IEnumerable<T> collection); public HashSet( IEqualityComparer<T> comparer); public HashSet(IEnumerable<T> collection, IEqualityComparer<T> comparer); //... } |
Dictionary<TKey, TValue> 和 HashSet<T> 類的構造函數都用到了 IEqualityComparer<T> 接口。
除了如上兩個,還有 ConcurrentDictionary<TKey, TValue>、SortedSet<T>、KeyedCollection<TKey, TItem>(抽象類)、SynchronizedKeyedCollection<K, T> 等等也使用 IEqualityComparer<T> 接口作為構造函數的參數。
?
IEqualityComparer<T> 作為參數多在復雜的重載中出現,滿足一些特殊情況的要求,而相應的簡單的重載確是經常使用的。因此,雖然 IEqualityComparer<T> 在 .net 應用廣泛,但在我們編程時,確是較少涉及。
不過話又說回來,一旦使用到時,就會感覺相當麻煩。多數時候你不得不去創建一個新類,去實現 IEqualityComparer<T> 接口,再去 new 一個實例,而你真正需要的可能僅僅是根據某個屬性(如 ID )進行比較。創建新類實現 IEqualityComparer<T> 接口,不但增加了代碼量,還增加的復雜度:你要考慮這個新類放在哪里合適,如何命名等等。
因此,我們期望有一個簡單的方法來能直接創建 IEqualityComparer<T> 的實例。《 c# 擴展方法 奇思妙用 基礎篇 八:Distinct 擴展 》一文中給出了一個簡單實用的類 CommonEqualityComparer<T, V>,在這里可以復用來達到我們的目標。
CommonEqualityComparer<T, V>
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<T, V> : IEqualityComparer<T> { private Func<T, V> keySelector; private IEqualityComparer<V> comparer; public CommonEqualityComparer(Func<T, V> keySelector, IEqualityComparer<V> comparer) { this .keySelector = keySelector; this .comparer = comparer; } public CommonEqualityComparer(Func<T, V> keySelector) : this (keySelector, EqualityComparer<V>.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<T> 的實例:
1 2 3 4 5 6 |
var dict = new Dictionary<Person, string >( new CommonEqualityComparer<Person, string >(p => p.Name) ); List<Person> persons = null ; Person p1 = null ; //... var ps = persons.Contains(p1, new CommonEqualityComparer<Person, int >(p=>p.ID) ); |
相信看了上面代碼的,你會覺得 new CommonEqualityComparer<Person, string>(p => p.Name)) 太冗長。不過我們可以借助下面的類加以改善:
1 2 3 4 5 6 7 8 9 10 11 |
public static class Equality<T> { public static IEqualityComparer<T> CreateComparer<V>(Func<T, V> keySelector) { return new CommonEqualityComparer<T, V>(keySelector); } public static IEqualityComparer<T> CreateComparer<V>(Func<T, V> keySelector, IEqualityComparer<V> comparer) { return new CommonEqualityComparer<T, V>(keySelector, comparer); } } |
調用代碼可簡化:
1 2 |
var dict = new Dictionary<Person, string >( Equality<Person>.CreateComparer(p => p.Name) ); var ps = persons.Contains(p1, Equality<Person>.CreateComparer(p => p.ID) ); |
不考慮類名和方法名的前提下, Equality<Person>.CreateComparer(p => p.ID) 的寫法也經精簡到極限了 (如果你能進一步精簡,不妨告訴我) 。
其實有了 Equality<T> 這個類,我們大可將 CommonEqualityComparer<T, V> 類封裝隱藏起來。
Equality<T> 類
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<T> { public static IEqualityComparer<T> CreateComparer<V>(Func<T, V> keySelector) { return new CommonEqualityComparer<V>(keySelector); } public static IEqualityComparer<T> CreateComparer<V>(Func<T, V> keySelector, IEqualityComparer<V> comparer) { return new CommonEqualityComparer<V>(keySelector, comparer); } class CommonEqualityComparer<V> : IEqualityComparer<T> { private Func<T, V> keySelector; private IEqualityComparer<V> comparer; public CommonEqualityComparer(Func<T, V> keySelector, IEqualityComparer<V> comparer) { this .keySelector = keySelector; this .comparer = comparer; } public CommonEqualityComparer(Func<T, V> keySelector) : this (keySelector, EqualityComparer<V>.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<T, V> 封裝成了 Equaility<T> 的嵌套類 CommonEqualityComparer<V>,對外不可見,降低了使用的復雜度。
《 c# 擴展方法 奇思妙用 基礎篇 八:Distinct 擴展 》一文中的 Distinct 擴展方法 寫起來也簡單了:
1 2 3 4 5 6 7 8 9 10 11 |
public static class DistinctExtensions { public static IEnumerable<T> Distinct<T, V>( this IEnumerable<T> source, Func<T, V> keySelector) { return source.Distinct( Equality<T>.CreateComparer(keySelector) ); } public static IEnumerable<T> Distinct<T, V>( this IEnumerable<T> source, Func<T, V> keySelector, IEqualityComparer<V> comparer) { return source.Distinct (Equality<T>.CreateComparer(keySelector, comparer) ); } } |
Linq 中除 Distinct 外還有眾多方法使用了 IEqualityComparer<T> 接口,逐一擴展未必是一個好方式,使用 Equality<T>.CreateComparer 方法比較明智。
總結
.net 中經常把 IEqualityComparer<T> 用作某些重載的參數。
雖然這些重載在日常使用中并不頻繁,不過一旦用到,大多要創建新類實現 IEqualityComparer<T>,繁瑣不給力。
本文創建 Equality<T> 泛型類,配合一個 lambda 表達式可快速創建 IEqualityComparer<T> 的實例。
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
