简体   繁体   English

使用IEqualityComparer <T>的推荐最佳做法是什么?

[英]What's the recommended best practice for using IEqualityComparer<T>?

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

Any time you consider using an IEqualityComparer<T> , pause to think if the class could be made to implement IEquatable<T> instead. IEquatable<T>考虑使用IEqualityComparer<T> ,请暂停思考是否可以使类实现IEquatable<T> If a Product should always be compared by ID, just define it to be equated as such so you can use the default comparer. 如果应始终通过ID比较Product ,只需将其定义为等同,以便您可以使用默认比较器。

That said, there are still a few of reasons you might want a custom comparer: 也就是说,您可能还需要一些自定义比较器的原因:

  1. If there are multiple ways instances of a class could be considered equal. 如果有多种方式可以认为类的实例是相等的。 The best example of this is a string, for which the framework provides six different comparers in StringComparer . 最好的例子是一个字符串,框架为StringComparer提供了六个不同的比较器。
  2. If the class is defined in such a way that you can't define it as IEquatable<T> . 如果以这样的方式定义类,则无法将其定义为IEquatable<T> This would include classes defined by others and classes generated by the compiler (specifically anonymous types, which use a property-wise comparison by default). 这将包括由其他人定义的类和由编译器生成的类(特别是匿名类型,默认情况下使用属性方式比较)。

If you do decide you need a comparer, you can certainly use a generalized comparer (see DMenT's answer), but if you need to reuse that logic you should encapsulate it in a dedicated class. 如果您确定需要比较器,您当然可以使用通用比较器(请参阅DMenT的答案),但如果您需要重用该逻辑,则应将其封装在专用类中。 You could even declare it by inheriting from the generic base: 您甚至可以通过继承通用基础来声明它:

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

As far as use, you should take advantage of comparers when possible. 就使用而言,您应尽可能利用比较器。 For example, rather than calling ToLower() on every string used as a dictionary key (logic for which will be strewn across your app), you should declare the dictionary to use a case-insensitive StringComparer . 例如,您应该声明字典使用不区分大小写的StringComparer ,而不是在用作字典键的每个字符串上调用ToLower() (逻辑将在您的应用程序中散布)。 The same goes for the LINQ operators that accept a comparer. 接受比较器的LINQ运算符也是如此。 But again, always consider if the equatable behavior that should be intrinsic to the class rather than defined externally. 但同样,要始终考虑应该是类的固有行为而不是外部定义的等同行为。

I did the following, I'm not sure if it is real-world best practice, but it worked fine for me. 我做了以下,我不确定它是否是真实世界的最佳实践,但它对我来说很好。 :) :)

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
}

Then you can use it in your collections. 然后你可以在你的收藏中使用它。

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);

If you need to support custom GetHashCode() functionality, use the alternative constructor to provide a lambda to do the alternative calculation: 如果需要支持自定义GetHashCode()功能,请使用备用构造函数提供lambda来执行备用计算:

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

I hope this helps. 我希望这有帮助。 =) =)

See this post for (better) alternatives: Wrap a delegate in an IEqualityComparer 有关(更好)备选方案,请参阅此文章: 在IEqualityComparer中包装委托

Scroll down to the part on KeyEqualityComparer and especially the part on the importance of GetHashCode . 向下滚动到KeyEqualityComparer上的部分,尤其是关于GetHashCode重要性的部分。 There is a whole discussion on why obj.GetHashCode(); 关于为什么obj.GetHashCode();有一个完整的讨论 obj.GetHashCode(); (as suggested by DMenT's post) is wrong and should just return 0 instead. (正如DMenT的帖子所建议的那样)是错误的,应该只返回0。

This is what MSDN has to say about IEqualityComparer (non-generic): 这就是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. 也就是说,您可以创建自己的相等定义,并指定此定义与接受IEqualityComparer接口的集合类型一起使用。 In the .NET Framework, constructors of the Hashtable , NameValueCollection , and OrderedDictionary collection types accept this interface. 在.NET Framework中, HashtableNameValueCollectionOrderedDictionary集合类型的构造函数接受此接口。

This interface supports only equality comparisons. 此接口仅支持相等比较。 Customization of comparisons for sorting and ordering is provided by the IComparer interface. IComparer接口提供了用于排序和排序的比较的定制。

It looks like the generic version of this interface performs the same function but is used for Dictionary<(Of <(TKey, TValue>)>) collections. 看起来此接口的通用版本执行相同的功能,但用于Dictionary<(Of <(TKey, TValue>)>)集合。

As far as best practices around using this interface for your own purposes. 至于为您自己的目的使用此接口的最佳实践。 I would say that the best practice would be to use it when you are deriving or implementing a class that has similar functionality to the above mentioned .NET framework collections and where you want to add the same capability to your own collections. 我想说最好的做法是在你派生或实现一个与上面提到的.NET框架集合具有类似功能的类时,以及你想为你自己的集合添加相同功能的类中使用它。 This will ensure that you are consistent with how the .NET framework uses the interface. 这将确保您与.NET框架使用接口的方式保持一致。

In other words support the use of this interface if you are developing a custom collection and you want to allow your consumers to control equality which is used in a number of LINQ and collection related methods (eg. Sort). 换句话说,如果您正在开发自定义集合并且希望允许您的使用者控制在许多LINQ和集合相关方法(例如,排序)中使用的相等性,则支持使用此接口。

I would say that the best use would be when you need to plug in different equality rules for a certain algorithm. 我想说最好用的是当你需要为某个算法插入不同的相等规则时。 Much in the same way that a sorting algorithm might accept an IComparer<T> , a finding algorithm might accept an IEqualityComparer<T> 与排序算法可能接受IComparer<T>方式大致相同,查找算法可能会接受IEqualityComparer<T>

The list uses this interface alot, so you can say a.Substract(b) or other of these nice functions. 该列表使用了这个界面,所以你可以说a.Substract(b)或其他这些不错的函数。

Just remember: If you're objects don't return the same Hashcode, the Equals is not called. 请记住:如果您的对象不返回相同的Hashcode,则不会调用Equals。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM