首选EqualityComparer <t>到IEqualityComparer <t> </t> </t>

时间:2011-04-18 18:40:42

标签: c# .net

来自MSDN上的IEqualityComparer<T>备注部分:

  1.   

    我们建议您从中获取   EqualityComparer&LT; T&GT;而不是   实现IEqualityComparer&lt; T&gt;   界面,因为   EqualityComparer&LT; T&GT;班级考试   平等使用   IEquatable&lt; T&gt; .Equals方法代替   Object.Equals方法。 ...

    • 我不明白引用的论点,为什么我们宁愿从EqualityComparer<T>类派生而不是实现IEqualityComparer<T>。这意味着实现IEqualityComparer<T>的对象将使用Object.Equals测试相等性,但当我们不想使用{{1}测试相等性时,不是实现IEqualityComparer<T>的全部要点}或Object.Equals

    • 它还暗示如果我们从IEquatable<T>.Equals派生,那么派生类将使用EqualityComparer<T>方法测试相等性。同样,当我们不想使用IEquatable<T>.EqualsEqualityComparer<T>(因为Object.Equals已使用{{IEquatable<T>.Equals进行测试时,不是从EqualityComparer<T>.Default派生的全部内容1}}或Object.Equals)?

  2.   

    ......这与此一致   包含,IndexOf,LastIndexOf和   删除Dictionary&lt; TKey的方法,   TValue&GT;类和其他通用的   集合。

    • 我假设.NET库中的大多数集合测试默认元素的相等性(即当用户不向这些集合提供自己的自定义IEquatable<T>.Equals对象时)通过IEqualityComparer<T>调用IEquatable<T>.EqualsObject.Equals(取决于T类型的元素是否实现IEquatable<T>)。

    • 为什么这些集合(在测试默认相等时)不直接调用EqualityComparer<T>.DefaultIEquatable<T>.Equals而不是Object.Equals类?< / p>

4 个答案:

答案 0 :(得分:25)

关于你的第一个问题:

它说的实际上没用,因为IEqualityComparer<T>接口的两个方法都标记为抽象。它们都没有“默认”实现,无论如何你都必须覆盖它。如果有的话,他们在这里提供的“推理”听起来更像是比较者可能所做的事情的指导,而且与实际做的无关。

查看EqualityComparer<T>类的公共/受保护接口,只有一个兑换质量,它实现了非通用IEqualityComparer接口。我认为他们的意思是他们推荐它,因为EqualityComparer<T>实际上实现了非通用IEqualityComparer接口,因为在需要非通用比较器的情况下可以使用您的类。

IComparer<T>的评论部分中更有意义:

  

我们建议您从Comparer&lt; T&gt;中获得。而不是实现IComparer&lt; T&gt;接口,因为Comparer&lt; T&gt; class提供IComparer.Compare方法的显式接口实现和获取对象的默认比较器的Default属性。

我怀疑它应该为IEqualityComparer<T>说些类似的东西,但是有些想法混杂在一起并最终得到了不完整的描述。


关于你的第二个问题:

图书馆中找到的馆藏的主要目标是尽可能灵活。一种方法是通过提供IComparer<T>IEqualityComparer<T>进行比较,允许自定义方式比较其中的对象。如果没有提供默认比较器的实例,那么获取默认比较器的实例比直接进行比较要容易得多。反过来,这些比较器可能包含调用恰当比较的必要逻辑。

例如,比较器可以确定T是否为IEquatable<T>并在对象上调用IEquatable<T>.Equals或以其他方式使用Object.Equals。更好地封装在比较器中,而不是在集合代码中可能重复。

此外,如果他们想要直接调用IEquatable<T>.Equals,他们必须在T上添加一个限制才能使此调用成为可能。这样做会降低其灵活性,并且不利于首先提供比较器的好处。

答案 1 :(得分:3)

我不明白1的建议。对我来说这似乎很奇怪。

至于2 - 很多时候,你最终会得到一个Dictionary的类型(例如IEqualityComparer<T>)。虽然实现可以存储一个空值并明确地调用Equals本身,但这样做会很痛苦 - 并且还会涉及到显着的丑陋以确保它没有框值不必要地实施IEquatable<T>的类型。使用界面,EqualityComparer<T>.Default 显着更简单,更一致。

答案 2 :(得分:0)

  

从基类派生类的主要原因是基类   class可以提供可以重用的代码,因此您不必编写代码   自己。

如果你从界面派生你的比较器,你必须创建 代码,你自己给你一个默认的比较器(当然只有你需要它,但嘿,每个人都想要免费的功能!)

班级EqualityComparer使用factory design pattern

  

在Factory模式中,我们创建对象而不将创建逻辑暴露给客户端,并使用通用接口引用新创建的对象。

好消息是,EqualityComparer的所有用户只需调用prperty default,并为他们完成所有操作以创建暴露接口的正确对象IEqualtiyComparer

这样做的好处是,如果你需要IEqualityComparer作为函数中的参数,那么你不必检查类T是否实现IEqualtiy<T>,字典就是这样做的对你而言。

如果您派生自EqualtityComparer<T> 并确保派生类遵循工厂设计模式,那么在几个相同的比较器之间切换很容易。

此外,与任何工厂一样,您只需更改工厂的参数即可生成完全不同的等式比较器。

当然你可以在不派生EqualtyComparer<T>的情况下创建一个相等比较器工厂,但是如果你派生你的工厂可以创建一个额外类型的相等比较器:默认的相等比较器,它是使用eiether { {1}}或Object.Equals。您不必为此编写任何额外的代码,只需派生!

  

你是否觉得从EqualtyComparer派生是否有用,取决于你是否认为工厂设计模式是有用的。

例如,假设您要检查两个字典是否相等。人们可以想到几个层次的平等:

  1. 如果字典X和Y是相同的对象
  2. ,则它们是相等的
  3. 如果X和Y具有相等的键(使用字典键比较器),并且它们的值是同一个对象,那么它们是相等的
  4. X和Y相等,如果它们具有相同的键(使用字典键比较器),并且如果它们的值相等,则使用IEquatable<T>的默认相等比较器
  5. X和Y相等,如果他们有相同的键(使用字典键比较器),并使用提供的相等比较器的值相等。
  6. 如果从EqualityComparer派生字典比较器类,则已经有comparer(1)。如果提供的TValue比较器来自EqualityComparer,则(3)和(4)之间没有真正的区别。

    因此,让我们派生出可以创建这四个比较器的工厂:

    TValue

    对于comparer(2),您可以使用stackoverflow IEqualityComparer that uses ReferenceEquals中描述的参考值比较器

    所以现在我们有四个不同的相等比较器,只为一个比较器提供代码。剩下的就被重复使用了!

    如果没有创建默认比较器的工厂,这种重用就不那么容易了

    比较器代码(4):使用提供的比较器检查TValue的等式

    class DictionaryComparerFactory<TKey, TValue> : 
        EqualitiyComparer<Dictionary<TKey, TValue>>
    {
        // By deriving from EqaulityComparer, you already have comparer (1)
        // via property Default
    
        // comparer (4):
        // X and Y are equal if equal keys and equal values using provided value comparer
        public static IEqualityComparer<Dictionary<TKey, TValue>>
            CreateContentComparer(IEqualityComparer<TValue> valueComparer)
        {
            return new DictionaryComparer<TKey, TValue>(valueComparer);
        }
    
        // comparer (3): X and Y equal if equal keys and values default equal
        // use (4) by providing the default TValue comparer
        public static IEqualityComparer<Dictionary<TKey, TValue>>
            CreateDefaultValueComparer(IEqualityComparer<TValue> valueComparer)
        {
            IEqualityComparer<TValue> defaultValueComparer =
                EqualtiyComparer<TValue>.Default;
            return new DictionaryComparer<TKey, TValue>(defaultValuecomparer);
        }
    
        // comparer (2): X and Y are equal if equal keys and values are same object
        // use reference equal for values
        public IEqualityComparer<TKey, TValue> CreateReferenceValueComparer()
        {
            IEqualityComparer<TValue> referenceValueComparer = ...
            return new DictionaryComparer<TKey, TValue>(referenceValuecomparer);
        }
    }
    

    现在我们可以简单地使用不同的比较器比较两个词典:

    // constructor
    protected DictionaryComparer(IEqualityComparer<TValue> valueComparer) : base()
    {   // if no comparer provided, use the default comparer
        if (Object.ReferenceEquals(valueComparer, null))
            this.valueComparer = EqualityComparer<TValue>.Default;
        else
            this.valueComparer = valueComparer
    }
    
    // comparer for TValue initialized in constructor
    protected readonly IEqualityComparer<TValue> valueComparer;
    
    public override bool Equals(Dictionary<TKey, TValue> x, Dictionary<TKey, TValue> y)
    {
        if (x == null) { return y == null; } 
        if (y == null) return false;
        if (Object.ReferenceEquals(x, y)) return true;
        if (x.GetType() != y.GetType()) return false;
    
        // now do equality checks according to (4)
        foreach (KeyValuePair<TKey, TValue> xKeyValuePair in x)
        {
            TValue yValue;
            if (y.TryGetValue(xKeyValuePair.Key, out yValue))
            {   // y also has x.Key. Are values equal?
                if (!this.valueComparer.Equals(xKeyValuePair.Value, yValue))
                {   // values are not equal
                    return false;
                }
                // else: values equal, continue with next key
            }
            else
            {   // y misses a key that is in x
                return false;
            }
        }
    
        // if here, all key/values equal
        return true;
    }
    

    因此推导导致我的相等比较工厂总是有一个合适的Defautlt比较器。让我自己编写代码

答案 3 :(得分:0)

如果您在MSDN解释中更改一个词,即将派生为 use ,这将变得更加有意义。

在MSDN上: EqualityComparer<T>

我们建议您(而非派生)使用类,而不要实现EqualityComparer<T>接口,因为IEqualityComparer<T>类会进行测试使用EqualityComparer<T>方法而不是IEquatable<T>.Equals方法实现平等。这与Dictionary类和其他通用集合的Object.EqualsContainsIndexOfLastIndexOf方法是一致的。

当然,这仅在T实现Remove

时有效

请注意,奇怪的是,只有IEquality<T>Array拥有List<T>IndexOf方法,并且对于任何一种方法来说,没有重载占用LastIndexOf 。其他通用集合的构造函数采用IEqualityComparer<T>

在MSDN上: IEqualityComparer<T>

我们建议您(而非派生)从Comparer<T>:使用,而不要实现Comparer<T>接口,因为IComparer<T>类提供Comparer<T>方法和Default属性的显式接口实现,该属性获取对象的默认比较器。

当然,这仅在T实现IComparer.CompareIComparable

时有效

如果T未实现从IComparable<T>EqualityComparer<T>派生的所需接口,则很有用,因为它免费提供了非通用接口的实现。

另一方面,实现Comparer<T>IEqualityComparer<T>可以提高性能,因为它可以跳过对IComparer<T>IEquatable<T>的调用。