多个Linq.Enumerable函数采用IEqualityComparer<T>
。是否有一个方便的包装器类适合a delegate(T,T)=>bool
实现IEqualityComparer<T>
?编写一个代码很容易(如果您忽略定义正确的哈希码的问题),但是我想知道是否有一个现成的解决方案。
具体来说,我想对Dictionary
s 进行设置操作,仅使用Key定义成员资格(同时根据不同规则保留值)。
多个Linq.Enumerable函数采用IEqualityComparer<T>
。是否有一个方便的包装器类适合a delegate(T,T)=>bool
实现IEqualityComparer<T>
?编写一个代码很容易(如果您忽略定义正确的哈希码的问题),但是我想知道是否有一个现成的解决方案。
具体来说,我想对Dictionary
s 进行设置操作,仅使用Key定义成员资格(同时根据不同规则保留值)。
Answers:
通常,我可以通过在答案上评论@Sam来解决此问题(我对原始帖子进行了一些编辑,以在不更改行为的情况下进行一些整理。)
以下是我对@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 );
}
}
GetHashCode
其他人已经评论了一个事实,即任何定制IEqualityComparer<T>
实现都应该真正包含一个GetHashCode
方法 ; 但是没有人愿意详细解释原因。
这就是为什么。您的问题特别提到了LINQ扩展方法。几乎所有这些都依靠哈希码来正常工作,因为它们在内部利用哈希表来提高效率。
以Distinct
为例。如果所有扩展方法都是一种Equals
方法,请考虑此扩展方法的含义。如果只有,您如何确定某个项目是否已按顺序扫描Equals
?您列举了已经查看过的所有值,并检查是否匹配。这将导致Distinct
使用最坏情况的O(N 2)算法而不是O(N )算法!
幸运的是,事实并非如此。Distinct
不只是用Equals
; 它也使用GetHashCode
。实际上,如果没有提供适当的,它绝对不能正常工作IEqualityComparer<T>
GetHashCode
。以下是一个人为设计的示例,说明了这一点。
说我有以下类型:
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);
}
}
现在说我有一个List<Value>
,我想找到所有具有不同名称的元素。这是Distinct
使用自定义相等比较器的理想用例。因此,让我们使用Aku的答案中的Comparer<T>
类:
var comparer = new Comparer<Value>((x, y) => x.Name == y.Name);
现在,如果我们有一堆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);
}
输出:
x:1346013431 x:1388845717 x:1576754134 x:1104067189 x:1144789201 x:1862076501 x:1573781440 x:646797592 x:655632802 x:1206819377
嗯,那没用,是吗?
那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);
}
}
输出:
[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
再说一次:没有用。
如果您考虑一下,在内部Distinct
使用HashSet<T>
(或等效的)内部GroupBy
使用类似的东西就很有意义Dictionary<TKey, List<T>>
。这可以解释为什么这些方法不起作用吗?让我们尝试一下:
var uniqueValues = new HashSet<Value>(values, comparer);
foreach (Value x in uniqueValues)
{
Console.WriteLine(x);
}
输出:
x:1346013431 x:1388845717 x:1576754134 x:1104067189 x:1144789201 x:1862076501 x:1573781440 x:646797592 x:655632802 x:1206819377
是的...开始有意义了吗?
希望从这些示例中可以清楚地看出,为什么GetHashCode
在任何IEqualityComparer<T>
实现中都包含适当的内容如此重要。
扩展orip的答案:
这里可以进行一些改进。
Func<T, TKey>
而不是Func<T, object>
; 这样可以防止实际中将值类型键装箱keyExtractor
。where TKey : IEquatable<TKey>
约束。这将防止在Equals
调用中装箱(object.Equals
带有object
参数;您需要一个IEquatable<TKey>
实现来带TKey
参数而不将其装箱)。显然,这可能构成了过于严格的限制,因此您可以创建没有约束的基类和带有约束的派生类。结果代码如下所示:
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));
}
}
StrictKeyEqualityComparer.Equals
方法似乎与相同KeyEqualityComparer.Equals
。请问TKey : IEquatable<TKey>
约束化妆TKey.Equals
工作方式不同?
TKey
可能是任意类型,因此编译器将使用虚拟方法Object.Equals
,该方法需要将值类型参数装箱,例如int
。但是,在后一种情况下,由于TKey
受限于实施IEquatable<TKey>
,TKey.Equals
将使用不需要任何装箱的方法。
StringKeyEqualityComparer<T, TKey>
也不需要。
当您要自定义相等性检查时,您有99%的时间对定义要比较的键感兴趣,而不是比较本身。
这可能是一个优雅的解决方案(来自Python的list sort方法的概念)。
用法:
var foo = new List<string> { "abc", "de", "DE" };
// case-insensitive distinct
var distinct = foo.Distinct(new KeyEqualityComparer<string>( x => x.ToLower() ) );
本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();
}
}
恐怕没有现成的包装器。但是创建一个并不难:
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));
private readonly Func<T, int> _hashCodeResolver
也必须在构造函数中传递并在GetHashCode(...)
方法中使用。
obj.ToString().ToLower().GetHashCode()
代替obj.GetHashCode()
?
IEqualityComparer<T>
总是在后台使用哈希的位置(例如LINQ的GroupBy,Distinct,Except,Join等)以及有关哈希的MS合同已被破坏。这是MS的文档摘录:“需要执行一些操作,以确保如果Equals方法对两个对象x和y返回true,则GetHashCode方法为x返回的值必须等于为y返回的值。” 请参阅:msdn.microsoft.com/en-us/library/ms132155
与Dan Tao的答案相同,但有一些改进:
依靠进行EqualityComparer<>.Default
实际比较,从而避免对struct
已实现的值类型进行装箱IEquatable<>
。
自从EqualityComparer<>.Default
使用以来,它不会爆炸null.Equals(something)
。
提供的静态包装器IEqualityComparer<>
将具有用于创建比较器实例的静态方法-简化了调用。比较
Equality<Person>.CreateComparer(p => p.ID);
与
new EqualityComparer<Person, int>(p => p.ID);
添加了用于指定IEqualityComparer<>
密钥的重载。
班上:
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));
}
}
}
您可以这样使用它:
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);
人是一个简单的类:
class Person
{
public int ID { get; set; }
public string Name { get; set; }
public DateTime Birthday { get; set; }
}
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 );
}
}
带有扩展名:-
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 ) );
}
}
我要回答我自己的问题。要将字典视为集合,最简单的方法似乎是将集合操作应用于dict.Keys,然后使用Enumerable.ToDictionary(...)转换回Dictionary。
的实现(德语文本)使用lambda表达式实现IEqualityCompare 关心空值,并使用扩展方法生成IEqualityComparer。
要在Linq联合中创建IEqualityComparer,您只需编写
persons1.Union(persons2, person => person.LastName)
比较器:
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();
}
}
您还需要添加扩展方法以支持类型推断
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));
}
}
只是一项优化:我们可以使用开箱即用的EqualityComparer进行价值比较,而不是委派它。
由于实际的比较逻辑现在保留在您可能已经重载的GetHashCode()和Equals()中,因此这也可以使实现更简洁。
这是代码:
public class MyComparer<T> : IEqualityComparer<T>
{
public bool Equals(T x, T y)
{
return EqualityComparer<T>.Default.Equals(x, y);
}
public int GetHashCode(T obj)
{
return obj.GetHashCode();
}
}
不要忘记在对象上重载GetHashCode()和Equals()方法。
这篇文章对我有帮助: c#比较两个通用值
苏希尔
orip的答案很好。扩展orip的答案:
我认为解决方案的关键是使用“扩展方法”来转移“匿名类型”。
public static class Comparer
{
public static IEqualityComparer<T> CreateComparerForElements<T>(this IEnumerable<T> enumerable, Func<T, object> keyExtractor)
{
return new KeyEqualityComparer<T>(keyExtractor);
}
}
用法:
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();
public static Dictionary<TKey, TValue> Distinct<TKey, TValue>(this IEnumerable<TValue> items, Func<TValue, TKey> selector)
{
Dictionary<TKey, TValue> result = null;
ICollection collection = items as ICollection;
if (collection != null)
result = new Dictionary<TKey, TValue>(collection.Count);
else
result = new Dictionary<TKey, TValue>();
foreach (TValue item in items)
result[selector(item)] = item;
return result;
}
这样就可以选择具有lambda的属性,如下所示: .Select(y => y.Article).Distinct(x => x.ArticleID);
我不知道现有的课程,但类似:
public class MyComparer<T> : IEqualityComparer<T>
{
private Func<T, T, bool> _compare;
MyComparer(Func<T, T, bool> compare)
{
_compare = compare;
}
public bool Equals(T x, Ty)
{
return _compare(x, y);
}
public int GetHashCode(T obj)
{
return obj.GetHashCode();
}
}
注意:我尚未真正编译并运行此程序,因此可能存在输入错误或其他错误。
IEqualityComparer<T>
遗漏的东西GetHashCode
都是直截了当的。