通用IEqualityComparer 和GetHashCode

对于实现大量的IEqualityComparers有些懒惰,并且考虑到我无法轻松编辑被比较对象的类实现,我使用了以下内容,意在与Distinct()和Except()扩展方法一起使用。 :

public class GenericEqualityComparer : IEqualityComparer { Func compareFunction; Func hashFunction; public GenericEqualityComparer(Func compareFunction, Func hashFunction) { this.compareFunction = compareFunction; this.hashFunction = hashFunction; } public bool Equals(T x, T y) { return compareFunction(x, y); } public int GetHashCode(T obj) { return hashFunction(obj); } } 

看起来很不错,但每次真的需要一个哈希函数吗? 我知道哈希码用于将对象放入存储桶中。 不同的桶,对象不相等,并且不调用相等。

如果GetHashCode返回相同的值,则调用equals。 (来自: 为什么在重写Equals方法时重写GetHashCode很重要? )

所以可能出现问题,例如(我听到很多程序员惊恐地尖叫),GetHashCode返回一个常量,强制调用Equal?

没有什么会出错,但在基于散列表的容器中,在进行查找时,您将从大约O(1)变为O(n)性能。 简单地将所有内容存储在List中并蛮力地搜索它以查找满足相等性的项目。

如果一个常见的用例是根据一个属性比较对象,你可以添加一个额外的构造函数并实现并调用它:

 public GenericEqualityComparer(Func projection) { compareFunction = (t1, t2) => projection(t1).Equals(projection(t2)); hashFunction = t => projection(t).GetHashCode(); } var comaparer = new GenericEqualityComparer( o => o.PropertyToCompare); 

这将自动使用属性实现的哈希。

编辑:一个更有效和更强大的实现启发了我的Marc评论:

 public static GenericEqualityComparer Create(Func projection) { return new GenericEqualityComparer( (t1, t2) => EqualityComparer.Default.Equals( projection(t1), projection(t2)), t => EqualityComparer.Default.GetHashCode(projection(t))); } var comparer = GenericEqualityComparer.Create( o => o.PropertyToCompare); 

你的表现将会消失。 在集合数据结构上实现时, DistinctExcept是有效的操作。 通过提供一个恒定的哈希值,你基本上破坏了这个特性,并使用线性搜索强制简单算法。

您需要查看这是否适用于您的数据量。 但对于稍微大一些的数据集,差异将会很明显。 例如, Except将从预期时间O( n )增加到O(n²),这可能是一个大问题。

为什么不只是调用对象自己的GetHashCode方法而不是提供常量? 它可能不会给出特别好的值,但它不能比使用常量更糟糕,并且除非重写对象的GetHashCode方法以返回错误的值,否则仍将保留正确性。

在CodeProject上找到了这个 – Linq Distinct()的Generic IEqualityComparer很好地完成了。

使用案例:

 IEqualityComparer c = new PropertyComparer("Name"); IEnumerable distinctEmails = collection.Distinct(c); 

通用IEqualityComparer

 public class PropertyComparer : IEqualityComparer { private PropertyInfo _PropertyInfo; ///  /// Creates a new instance of PropertyComparer. ///  /// The name of the property on type T /// to perform the comparison on. public PropertyComparer(string propertyName) { //store a reference to the property info object for use during the comparison _PropertyInfo = typeof(T).GetProperty(propertyName, BindingFlags.GetProperty | BindingFlags.Instance | BindingFlags.Public); if (_PropertyInfo == null) { throw new ArgumentException(string.Format("{0} is not a property of type {1}.", propertyName, typeof(T))); } } #region IEqualityComparer Members public bool Equals(T x, T y) { //get the current value of the comparison property of x and of y object xValue = _PropertyInfo.GetValue(x, null); object yValue = _PropertyInfo.GetValue(y, null); //if the xValue is null then we consider them equal if and only if yValue is null if (xValue == null) return yValue == null; //use the default comparer for whatever type the comparison property is. return xValue.Equals(yValue); } public int GetHashCode(T obj) { //get the value of the comparison property out of obj object propertyValue = _PropertyInfo.GetValue(obj, null); if (propertyValue == null) return 0; else return propertyValue.GetHashCode(); } #endregion } 

试试这段代码:

 public class GenericCompare : IEqualityComparer where T : class { private Func _expr { get; set; } public GenericCompare(Func expr) { this._expr = expr; } public bool Equals(T x, T y) { var first = _expr.Invoke(x); var sec = _expr.Invoke(y); if (first != null && first.Equals(sec)) return true; else return false; } public int GetHashCode(T obj) { return obj.GetHashCode(); } } 

示例:collection = collection.Except(ExistedDataEles,new GenericCompare(x => x.Id))。ToList();