建议使用IEqualityComparer <T>的最佳实践是什么?

时间:2020-03-05 18:56:25  来源:igfitidea点击:

我正在寻找现实世界中的最佳实践,其他人如何实现具有复杂域的解决方案。

解决方案

回答

这是MSDN关于IEqualityComparer(非通用)的说法:

This interface allows the implementation of customized equality comparison for collections. That is, you can create your own definition of equality, and specify that this definition be used with a collection type that accepts the IEqualityComparer interface. In the .NET Framework, constructors of the Hashtable, NameValueCollection, and OrderedDictionary collection types accept this interface. 
  
  This interface supports only equality comparisons. Customization of comparisons for sorting and ordering is provided by the IComparer interface.

看起来该接口的通用版本执行相同的功能,但用于Dictionary &lt;(Of &lt;(TKey,TValue>)>)集合。

关于围绕此接口用于我们自己的目的的最佳实践。我要说的最佳实践是在派生或者实现一个具有与上述.NET框架集合相似的功能的类,并且希望在自己的集合中添加相同功能的类时使用它。这将确保我们与.NET框架使用接口的方式保持一致。

换句话说,如果我们正在开发自定义集合,并且希望允许使用者控制在许多LINQ和与集合相关的方法(例如,排序)中使用的相等性,则支持使用此接口。

回答

我会说最好的用法是当我们需要为某个算法插入不同的相等性规则时。与排序算法可以接受" IComparer <T>"的方式几乎相同,查找算法可以接受" IEqualityComparer <T>"的方式

回答

该列表大量使用此接口,因此我们可以说a.Substract(b)或者其他一些不错的函数。

只需记住:如果对象没有返回相同的哈希码,则不会调用Equals。

回答

我做了以下事情,我不确定这是否是现实世界中的最佳实践,但对我来说效果很好。 :)

public class GenericEqualityComparer<T> : IEqualityComparer<T>
{
    private Func<T, T, Boolean> _comparer;
    private Func<T, int> _hashCodeEvaluator;
    public GenericEqualityComparer(Func<T, T, Boolean> comparer)
    {
        _comparer = comparer;
    }

    public GenericEqualityComparer(Func<T, T, Boolean> comparer, Func<T, int> hashCodeEvaluator)
    {
        _comparer = comparer;
        _hashCodeEvaluator = hashCodeEvaluator;
    }

    #region IEqualityComparer<T> Members

    public bool Equals(T x, T y)
    {
        return _comparer(x, y);
    }

    public int GetHashCode(T obj)
    {
        if(obj == null) {
            throw new ArgumentNullException("obj");
        }
        if(_hashCodeEvaluator == null) {
            return 0;
        } 
        return _hashCodeEvaluator(obj);
    }

    #endregion
}

然后,我们可以在收藏夹中使用它。

var comparer = new GenericEqualityComparer<ShopByProduct>((x, y) => x.ProductId == y.ProductId);
var current = SelectAll().Where(p => p.ShopByGroup == group).ToList();
var toDelete = current.Except(products, comparer);
var toAdd = products.Except(current, comparer);

如果需要支持自定义GetHashCode()功能,请使用替代构造函数提供一个lambda来进行替代计算:

var comparer = new GenericEqualityComparer<ShopByProduct>(
       (x, y) => { return x.ProductId == y.ProductId; }, 
       (x)    => { return x.Product.GetHashCode()}
);

我希望这有帮助。 =)

回答

每当我们考虑使用IEqualityComparer &lt;T>时,请停下来思考是否可以使该类实现IEquatable &lt;T>。如果应该始终通过ID比较"产品",只需将其定义为等同,这样就可以使用默认的比较器。

就是说,我们仍然可能需要自定义比较器的原因有很多:

  • 如果有多种方法,则可以将一个类的实例视为相等。最好的例子是一个字符串,为此框架在StringComparer中提供了六个不同的比较器。
  • 如果以无法将其定义为" IEquatable <T>"的方式定义该类。这将包括其他人定义的类和编译器生成的类(特别是匿名类型,默认情况下使用基于属性的比较)。

如果确定需要比较器,则可以使用通用比较器(请参阅DMenT的答案),但是如果需要重用该逻辑,则应将其封装在专用类中。我们甚至可以通过从通用库继承来声明它:

class ProductByIdComparer : GenericEqualityComparer<ShopByProduct>
{
    public ProductByIdComparer()
        : base((x, y) => x.ProductId == y.ProductId, z => z.ProductId)
    { }
}

就使用而言,应尽可能利用比较器。例如,我们应该声明字典使用不区分大小写的StringComparer,而不是在用作字典键的每个字符串上调用" ToLower()"(逻辑将散布在应用程序中)。接受比较器的LINQ运算符也是如此。但同样,请始终考虑是否该类的固有行为应该是该类固有的,而不是在外部定义的。