昨日看到一篇文章 《 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元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元

