C# 使用 IEqualityComparer<T> 的推荐最佳实践是什么?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/74032/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-03 11:11:41  来源:igfitidea点击:

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

提问by Chris Canal

I'm looking for real world best practices, how other people might have implemented solutions with complex domains.

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

回答by Eric Schoonover

This is what MSDNhas 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 IEqualityComparerinterface. In the .NET Framework, constructors of the Hashtable, NameValueCollection, and OrderedDictionarycollection types accept this interface.

This interface supports only equality comparisons. Customization of comparisons for sorting and ordering is provided by the IComparerinterface.

此接口允许为集合实现自定义的相等性比较。也就是说,您可以创建自己的相等性定义,并指定此定义与接受IEqualityComparer接口的集合类型一起使用。在 .NET Framework 中HashtableNameValueCollection、 和OrderedDictionary集合类型的构造函数接受此接口。

此接口仅支持相等比较。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. This will ensure that you are consistent with how the .NET framework uses the interface.

至于围绕您自己的目的使用此界面的最佳实践。我认为最佳实践是在您派生或实现与上述 .NET 框架集合具有类似功能的类以及您希望向自己的集合添加相同功能的类时使用它。这将确保您与 .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 和集合相关方法(例如排序)中使用的相等性,则支持使用此接口。

回答by Chris Marasti-Georg

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>

回答by Jesper Blad Jensen

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.

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

回答by Jesper Blad Jensen

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. =)

我希望这有帮助。=)

回答by dahlbyk

Any time you consider using an IEqualityComparer<T>, pause to think if the class could be made to implement IEquatable<T>instead. If a Productshould always be compared by ID, just define it to be equated as such so you can use the default comparer.

每当您考虑使用 时IEqualityComparer<T>,请停下来想一想是否可以改为实现该类IEquatable<T>。如果 aProduct应始终按 ID 进行比较,只需将其定义为等号,以便您可以使用默认比较器。

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.
  2. If the class is defined in such a way that you can't define it as 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).
  1. 如果有多种方式可以将一个类的实例视为相等。最好的例子是一个字符串,框架为它提供了六个不同的比较器StringComparer
  2. 如果类的定义方式使您无法将其定义为IEquatable<T>. 这将包括其他人定义的类和编译器生成的类(特别是匿名类型,默认情况下使用属性比较)。

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. You could even declare it by inheriting from the generic base:

如果您确实决定需要比较器,则当然可以使用通用比较器(请参阅 DManT 的答案),但是如果您需要重用该逻辑,则应将其封装在专用类中。你甚至可以通过从泛型基类继承来声明它:

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. The same goes for the LINQ operators that accept a comparer. But again, always consider if the equatable behavior that should be intrinsic to the class rather than defined externally.

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

回答by Peet Brits

See this post for (better) alternatives: Wrap a delegate in an IEqualityComparer

有关(更好的)替代方案,请参阅这篇文章:Wrap a delegate in an IEqualityComparer

Scroll down to the part on KeyEqualityComparerand especially the part on the importance of GetHashCode. There is a whole discussionon why obj.GetHashCode();(as suggested by DMenT's post) is wrong and should just return 0 instead.

向下滚动到KeyEqualityComparer部分,尤其是关于GetHashCode 重要性的部分。关于为什么(如 DManT 的帖子所建议的)错误的原因进行了全面的讨论obj.GetHashCode();而应该只返回 0。