关于c#:使用IEqualityComparer < T >的推荐最佳实践是什么?

关于c#:使用IEqualityComparer < T >的推荐最佳实践是什么?

What's the recommended best practice for using IEqualityComparer?

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


每当您考虑使用IEqualityComparer< T >时,请停下来思考是否可以使该类实现IEquatable< T >。如果Product应该始终按ID进行比较,则只需将其定义为等价的即可,因此可以使用默认的比较器。

就是说,您仍然可能有一些原因需要使用自定义比较器:

  • 如果有多种方法,则可以将一个类的实例视为相等。最好的例子是字符串,框架在StringComparer中为其提供六个不同的比较器。
  • 如果以无法将其定义为IEquatable< T >的方式定义了该类。这将包括其他人定义的类和编译器生成的类(特别是匿名类型,默认情况下使用基于属性的比较)。
  • 如果确定需要比较器,则可以使用通用比较器(请参阅DMenT的答案),但是如果需要重用该逻辑,则应将其封装在专用类中。您甚至可以通过从通用库继承来声明它:

    1
    2
    3
    4
    5
    6
    class ProductByIdComparer : GenericEqualityComparer<ShopByProduct>
    {
        public ProductByIdComparer()
            : base((x, y) => x.ProductId == y.ProductId, z => z.ProductId)
        { }
    }

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


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

    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 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
    }

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

    1
    2
    3
    4
    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来进行替代计算:

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

    我希望这有帮助。 =)


    请参阅此帖子以了解(更好的)替代方法:将委托包装在IEqualityComparer中

    向下滚动到KeyEqualityComparer上的部分,尤其是有关GetHashCode重要性的部分。关于obj.GetHashCode();(如DMenT的帖子所建议的)为什么出错并且应该只返回0的原因,有完整的讨论。


    这是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<(Of <(TKey, TValue>)>)集合。

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

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


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

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


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


    推荐阅读