.net 在 IEqualityComparer 中包装一个委托
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/98033/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Wrap a delegate in an IEqualityComparer
提问by Marcelo Cantos
Several Linq.Enumerable functions take an IEqualityComparer<T>. Is there a convenient wrapper class that adapts a delegate(T,T)=>boolto implement IEqualityComparer<T>? It's easy enough to write one (if your ignore problems with defining a correct hashcode), but I'd like to know if there is an out-of-the-box solution.
几个 Linq.Enumerable 函数采用IEqualityComparer<T>. 是否有一个方便的包装类来适应delegate(T,T)=>bool实现IEqualityComparer<T>?编写一个很容易(如果您忽略定义正确哈希码的问题),但我想知道是否有开箱即用的解决方案。
Specifically, I want to do set operations on Dictionarys, using only the Keys to define membership (while retaining the values according to different rules).
具体来说,我想对Dictionarys进行 set 操作,仅使用 Keys 来定义成员资格(同时根据不同的规则保留值)。
采纳答案by Ruben Bartelink
Ordinarily, I'd get this resolved by commenting @Sam on the answer (I've done some editing on the original post to clean it up a bit without altering the behavior.)
通常,我会通过在答案上评论 @Sam 来解决这个问题(我已经对原始帖子进行了一些编辑以在不改变行为的情况下对其进行一些清理。)
The following is my riff of @Sam's answer, with a [IMNSHO] critical fix to the default hashing policy:-
以下是我对@Sam的回答的即兴演奏,对默认散列策略进行了 [IMNSHO] 关键修复:-
class FuncEqualityComparer<T> : IEqualityComparer<T>
{
readonly Func<T, T, bool> _comparer;
readonly Func<T, int> _hash;
public FuncEqualityComparer( Func<T, T, bool> comparer )
: this( comparer, t => 0 ) // NB Cannot assume anything about how e.g., t.GetHashCode() interacts with the comparer's behavior
{
}
public FuncEqualityComparer( Func<T, T, bool> comparer, Func<T, int> hash )
{
_comparer = comparer;
_hash = hash;
}
public bool Equals( T x, T y )
{
return _comparer( x, y );
}
public int GetHashCode( T obj )
{
return _hash( obj );
}
}
回答by Dan Tao
On the importance of GetHashCode
关于重要性 GetHashCode
Others have already commented on the fact that any custom IEqualityComparer<T>implementation should really include a GetHashCodemethod; but nobody's bothered to explain whyin any detail.
其他人已经评论过这样一个事实,即任何自定义IEqualityComparer<T>实现都应该真正包含一个GetHashCode方法;但没有人愿意详细解释原因。
Here's why. Your question specifically mentions the LINQ extension methods; nearly allof these rely on hash codes to work properly, because they utilize hash tables internally for efficiency.
这是为什么。您的问题特别提到了 LINQ 扩展方法;几乎所有这些都依赖于哈希码才能正常工作,因为它们在内部利用哈希表来提高效率。
Take Distinct, for example. Consider the implications of this extension method if all it utilized were an Equalsmethod. How do you determine whether an item's already been scanned in a sequence if you only have Equals? You enumerate over the entire collection of values you've already looked at and check for a match. This would result in Distinctusing a worst-case O(N2) algorithm instead of an O(N) one!
以Distinct为例。如果它使用的只是一种Equals方法,请考虑这种扩展方法的含义。如果您只有 ,您如何确定一个项目是否已经按顺序扫描过Equals?您枚举您已经查看过的整个值集合并检查是否匹配。这将导致Distinct使用最坏情况的 O(N 2) 算法而不是O(N ) 算法!
Fortunately, this isn't the case. Distinctdoesn't justuse Equals; it uses GetHashCodeas well. In fact, it absolutely does notwork properly without an IEqualityComparer<T>that supplies a proper GetHashCode. Below is a contrived example illustrating this.
幸运的是,情况并非如此。Distinct不只是使用Equals; 它也使用GetHashCode。事实上,这是绝对不正常没有工作IEqualityComparer<T>是提供一个合适的GetHashCode。下面是一个人为的例子来说明这一点。
Say I have the following type:
假设我有以下类型:
class Value
{
public string Name { get; private set; }
public int Number { get; private set; }
public Value(string name, int number)
{
Name = name;
Number = number;
}
public override string ToString()
{
return string.Format("{0}: {1}", Name, Number);
}
}
Now say I have a List<Value>and I want to find all of the elements with a distinct name. This is a perfect use case for Distinctusing a custom equality comparer. So let's use the Comparer<T>class from Aku's answer:
现在说我有一个List<Value>,我想找到所有具有不同名称的元素。这是Distinct使用自定义相等比较器的完美用例。所以让我们使用Aku 的答案中的Comparer<T>类:
var comparer = new Comparer<Value>((x, y) => x.Name == y.Name);
Now, if we have a bunch of Valueelements with the same Nameproperty, they should all collapse into one value returned by Distinct, right? Let's see...
现在,如果我们有一堆Value具有相同Name属性的元素,它们应该都折叠成一个由 返回的值Distinct,对吗?让我们来看看...
var values = new List<Value>();
var random = new Random();
for (int i = 0; i < 10; ++i)
{
values.Add("x", random.Next());
}
var distinct = values.Distinct(comparer);
foreach (Value x in distinct)
{
Console.WriteLine(x);
}
Output:
输出:
x: 1346013431 x: 1388845717 x: 1576754134 x: 1104067189 x: 1144789201 x: 1862076501 x: 1573781440 x: 646797592 x: 655632802 x: 1206819377
Hmm, that didn't work, did it?
嗯,那没用,是吗?
What about GroupBy? Let's try that:
怎么样GroupBy?让我们试试:
var grouped = values.GroupBy(x => x, comparer);
foreach (IGrouping<Value> g in grouped)
{
Console.WriteLine("[KEY: '{0}']", g);
foreach (Value x in g)
{
Console.WriteLine(x);
}
}
Output:
输出:
[KEY = 'x: 1346013431'] x: 1346013431 [KEY = 'x: 1388845717'] x: 1388845717 [KEY = 'x: 1576754134'] x: 1576754134 [KEY = 'x: 1104067189'] x: 1104067189 [KEY = 'x: 1144789201'] x: 1144789201 [KEY = 'x: 1862076501'] x: 1862076501 [KEY = 'x: 1573781440'] x: 1573781440 [KEY = 'x: 646797592'] x: 646797592 [KEY = 'x: 655632802'] x: 655632802 [KEY = 'x: 1206819377'] x: 1206819377
Again: didn't work.
再次:没有用。
If you think about it, it would make sense for Distinctto use a HashSet<T>(or equivalent) internally, and for GroupByto use something like a Dictionary<TKey, List<T>>internally. Could this explain why these methods don't work? Let's try this:
如果您考虑一下,在内部Distinct使用 a HashSet<T>(或等价物)并在内部GroupBy使用类似 a 的东西是有意义的Dictionary<TKey, List<T>>。这能解释为什么这些方法不起作用吗?让我们试试这个:
var uniqueValues = new HashSet<Value>(values, comparer);
foreach (Value x in uniqueValues)
{
Console.WriteLine(x);
}
Output:
输出:
x: 1346013431 x: 1388845717 x: 1576754134 x: 1104067189 x: 1144789201 x: 1862076501 x: 1573781440 x: 646797592 x: 655632802 x: 1206819377
Yeah... starting to make sense?
是啊...开始有意义?
Hopefully from these examples it's clear why including an appropriate GetHashCodein any IEqualityComparer<T>implementation is so important.
希望从这些示例中可以清楚为什么GetHashCode在任何IEqualityComparer<T>实现中包含适当的内容如此重要。
Original answer
原答案
Expanding on orip's answer:
扩展orip 的答案:
There are a couple of improvements that can be made here.
这里可以进行一些改进。
- First, I'd take a
Func<T, TKey>instead ofFunc<T, object>; this will prevent boxing of value type keys in the actualkeyExtractoritself. - Second, I'd actually add a
where TKey : IEquatable<TKey>constraint; this will prevent boxing in theEqualscall (object.Equalstakes anobjectparameter; you need anIEquatable<TKey>implementation to take aTKeyparameter without boxing it). Clearly this may pose too severe a restriction, so you could make a base class without the constraint and a derived class with it.
- 首先,我会用一个
Func<T, TKey>代替Func<T, object>;这将防止在实际keyExtractor本身中装箱值类型键。 - 其次,我实际上会添加一个
where TKey : IEquatable<TKey>约束;这将防止Equals调用中的装箱(object.Equals接受一个object参数;您需要一个IEquatable<TKey>实现来接受一个TKey参数而不对其进行装箱)。显然,这可能会造成过于严格的限制,因此您可以创建一个没有约束的基类和一个带有它的派生类。
Here's what the resulting code might look like:
生成的代码可能如下所示:
public class KeyEqualityComparer<T, TKey> : IEqualityComparer<T>
{
protected readonly Func<T, TKey> keyExtractor;
public KeyEqualityComparer(Func<T, TKey> keyExtractor)
{
this.keyExtractor = keyExtractor;
}
public virtual bool Equals(T x, T y)
{
return this.keyExtractor(x).Equals(this.keyExtractor(y));
}
public int GetHashCode(T obj)
{
return this.keyExtractor(obj).GetHashCode();
}
}
public class StrictKeyEqualityComparer<T, TKey> : KeyEqualityComparer<T, TKey>
where TKey : IEquatable<TKey>
{
public StrictKeyEqualityComparer(Func<T, TKey> keyExtractor)
: base(keyExtractor)
{ }
public override bool Equals(T x, T y)
{
// This will use the overload that accepts a TKey parameter
// instead of an object parameter.
return this.keyExtractor(x).Equals(this.keyExtractor(y));
}
}
回答by orip
When you want to customize equality checking, 99% of the time you're interested in defining the keys to compare by, not the comparison itself.
当您想要自定义相等性检查时,99% 的时间您感兴趣的是定义要比较的键,而不是比较本身。
This could be an elegant solution (concept from Python's list sort method).
这可能是一个优雅的解决方案(来自 Python列表排序方法的概念)。
Usage:
用法:
var foo = new List<string> { "abc", "de", "DE" };
// case-insensitive distinct
var distinct = foo.Distinct(new KeyEqualityComparer<string>( x => x.ToLower() ) );
The KeyEqualityComparerclass:
本KeyEqualityComparer类:
public class KeyEqualityComparer<T> : IEqualityComparer<T>
{
private readonly Func<T, object> keyExtractor;
public KeyEqualityComparer(Func<T,object> keyExtractor)
{
this.keyExtractor = keyExtractor;
}
public bool Equals(T x, T y)
{
return this.keyExtractor(x).Equals(this.keyExtractor(y));
}
public int GetHashCode(T obj)
{
return this.keyExtractor(obj).GetHashCode();
}
}
回答by aku
I'm afraid there is no such wrapper out-of-box. However it's not hard to create one:
恐怕没有这种开箱即用的包装器。然而,创建一个并不难:
class Comparer<T>: IEqualityComparer<T>
{
private readonly Func<T, T, bool> _comparer;
public Comparer(Func<T, T, bool> comparer)
{
if (comparer == null)
throw new ArgumentNullException("comparer");
_comparer = comparer;
}
public bool Equals(T x, T y)
{
return _comparer(x, y);
}
public int GetHashCode(T obj)
{
return obj.ToString().ToLower().GetHashCode();
}
}
...
Func<int, int, bool> f = (x, y) => x == y;
var comparer = new Comparer<int>(f);
Console.WriteLine(comparer.Equals(1, 1));
Console.WriteLine(comparer.Equals(1, 2));
回答by ldp615
Same as Dan Tao's answer, but with a few improvements:
与丹涛的回答相同,但有一些改进:
Relies on
EqualityComparer<>.Defaultto do the actual comparing so that it avoids boxing for value types (structs) that has implementedIEquatable<>.Since
EqualityComparer<>.Defaultused it doesn't explode onnull.Equals(something).Provided static wrapper around
IEqualityComparer<>which will have a static method to create the instance of comparer - eases calling. CompareEquality<Person>.CreateComparer(p => p.ID);with
new EqualityComparer<Person, int>(p => p.ID);Added an overload to specify
IEqualityComparer<>for the key.
依赖
EqualityComparer<>.Default于进行实际比较,以避免对struct已实现的值类型进行装箱IEquatable<>。由于
EqualityComparer<>.Default使用它不会发生爆炸null.Equals(something)。提供了静态包装器
IEqualityComparer<>,它将有一个静态方法来创建比较器的实例 - 简化调用。相比Equality<Person>.CreateComparer(p => p.ID);和
new EqualityComparer<Person, int>(p => p.ID);添加了重载以指定
IEqualityComparer<>键。
The class:
班上:
public static class Equality<T>
{
public static IEqualityComparer<T> CreateComparer<V>(Func<T, V> keySelector)
{
return CreateComparer(keySelector, null);
}
public static IEqualityComparer<T> CreateComparer<V>(Func<T, V> keySelector,
IEqualityComparer<V> comparer)
{
return new KeyEqualityComparer<V>(keySelector, comparer);
}
class KeyEqualityComparer<V> : IEqualityComparer<T>
{
readonly Func<T, V> keySelector;
readonly IEqualityComparer<V> comparer;
public KeyEqualityComparer(Func<T, V> keySelector,
IEqualityComparer<V> comparer)
{
if (keySelector == null)
throw new ArgumentNullException("keySelector");
this.keySelector = keySelector;
this.comparer = comparer ?? 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));
}
}
}
you may use it like this:
你可以这样使用它:
var comparer1 = Equality<Person>.CreateComparer(p => p.ID);
var comparer2 = Equality<Person>.CreateComparer(p => p.Name);
var comparer3 = Equality<Person>.CreateComparer(p => p.Birthday.Year);
var comparer4 = Equality<Person>.CreateComparer(p => p.Name, StringComparer.CurrentCultureIgnoreCase);
Person is a simple class:
Person 是一个简单的类:
class Person
{
public int ID { get; set; }
public string Name { get; set; }
public DateTime Birthday { get; set; }
}
回答by ldp615
public class FuncEqualityComparer<T> : IEqualityComparer<T>
{
readonly Func<T, T, bool> _comparer;
readonly Func<T, int> _hash;
public FuncEqualityComparer( Func<T, T, bool> comparer )
: this( comparer, t => t.GetHashCode())
{
}
public FuncEqualityComparer( Func<T, T, bool> comparer, Func<T, int> hash )
{
_comparer = comparer;
_hash = hash;
}
public bool Equals( T x, T y )
{
return _comparer( x, y );
}
public int GetHashCode( T obj )
{
return _hash( obj );
}
}
With extensions :-
带扩展名:-
public static class SequenceExtensions
{
public static bool SequenceEqual<T>( this IEnumerable<T> first, IEnumerable<T> second, Func<T, T, bool> comparer )
{
return first.SequenceEqual( second, new FuncEqualityComparer<T>( comparer ) );
}
public static bool SequenceEqual<T>( this IEnumerable<T> first, IEnumerable<T> second, Func<T, T, bool> comparer, Func<T, int> hash )
{
return first.SequenceEqual( second, new FuncEqualityComparer<T>( comparer, hash ) );
}
}
回答by Bruno
orip's answer is great.
orip 的回答很棒。
Here a little extension method to make it even easier:
这里有一个小扩展方法,使它更容易:
public static IEnumerable<T> Distinct<T>(this IEnumerable<T> list, Func<T, object> keyExtractor)
{
return list.Distinct(new KeyEqualityComparer<T>(keyExtractor));
}
var distinct = foo.Distinct(x => x.ToLower())
回答by Marcelo Cantos
I'm going to answer my own question. To treat Dictionaries as sets, the simplest method seems to be to apply set operations to dict.Keys, then convert back to Dictionaries with Enumerable.ToDictionary(...).
我要回答我自己的问题。要将 Dictionaries 视为集合,最简单的方法似乎是将集合操作应用于 dict.Keys,然后使用 Enumerable.ToDictionary(...) 转换回 Dictionaries。
回答by Fried
The implementation at (german text) Implementing IEqualityCompare with lambda expressioncares about null values and uses extension methods to generate IEqualityComparer.
(德语文本)用 lambda 表达式实现 IEqualityCompare 的实现关心空值并使用扩展方法来生成 IEqualityComparer。
To create an IEqualityComparer in a Linq union your just have to write
要在 Linq 联合中创建 IEqualityComparer,您只需编写
persons1.Union(persons2, person => person.LastName)
The comparer:
比较器:
public class LambdaEqualityComparer<TSource, TComparable> : IEqualityComparer<TSource>
{
Func<TSource, TComparable> _keyGetter;
public LambdaEqualityComparer(Func<TSource, TComparable> keyGetter)
{
_keyGetter = keyGetter;
}
public bool Equals(TSource x, TSource y)
{
if (x == null || y == null) return (x == null && y == null);
return object.Equals(_keyGetter(x), _keyGetter(y));
}
public int GetHashCode(TSource obj)
{
if (obj == null) return int.MinValue;
var k = _keyGetter(obj);
if (k == null) return int.MaxValue;
return k.GetHashCode();
}
}
You also need to add an extension method to support type inference
您还需要添加一个扩展方法来支持类型推断
public static class LambdaEqualityComparer
{
// source1.Union(source2, lambda)
public static IEnumerable<TSource> Union<TSource, TComparable>(
this IEnumerable<TSource> source1,
IEnumerable<TSource> source2,
Func<TSource, TComparable> keySelector)
{
return source1.Union(source2,
new LambdaEqualityComparer<TSource, TComparable>(keySelector));
}
}
回答by matrix
orip's answeris great. Expanding on orip's answer:
orip 的回答很棒。扩展orip的答案:
i think that the solution's key is use "Extension Method" to transfer the "anonymous type".
我认为解决方案的关键是使用“扩展方法”来转移“匿名类型”。
public static class Comparer
{
public static IEqualityComparer<T> CreateComparerForElements<T>(this IEnumerable<T> enumerable, Func<T, object> keyExtractor)
{
return new KeyEqualityComparer<T>(keyExtractor);
}
}
Usage:
用法:
var n = ItemList.Select(s => new { s.Vchr, s.Id, s.Ctr, s.Vendor, s.Description, s.Invoice }).ToList();
n.AddRange(OtherList.Select(s => new { s.Vchr, s.Id, s.Ctr, s.Vendor, s.Description, s.Invoice }).ToList(););
n = n.Distinct(x=>new{Vchr=x.Vchr,Id=x.Id}).ToList();

