Preferring EqualityComparer<T> to IEqualityComparer<

本文关键字:lt to IEqualityComparer gt Preferring EqualityComparer | 更新日期: 2023-09-27 17:50:34

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

  1. 我们建议您从类,而不是实现了IEqualityComparer界面,因为EqualityComparer类测试的等式IEquatable。Equals方法而不是对象。=方法. ...

    • 我不理解引言的论点,为什么我们应该更喜欢从EqualityComparer<T>类派生而不是实现IEqualityComparer<T>。这意味着实现IEqualityComparer<T>的对象将使用Object.Equals测试相等性,但是当我们不想使用Object.EqualsIEquatable<T>.Equals测试相等性时,实现IEqualityComparer<T>的全部意义不是吗?

    • 这也意味着如果我们从EqualityComparer<T>派生,那么派生类将使用IEquatable<T>.Equals方法测试是否相等。再一次,当我们不想使用Object.EqualsIEquatable<T>.Equals测试相等性时,从EqualityComparer<T>派生的全部意义不是吗(因为EqualityComparer<T>.Default已经使用Object.EqualsIEquatable<T>.Equals进行测试了)?

  2. …这与包含,IndexOf, LastIndexOf,和删除Dictionary<TKey;类和其他泛型集合。>

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

    • 为什么这些集合(当测试默认相等时)不直接调用IEquatable<T>.EqualsObject.Equals,而不是通过EqualityComparer<T>.Default类?

Preferring EqualityComparer<T> to IEqualityComparer<

关于你的第一个问题:

IEqualityComparer<T>类的注释部分似乎并没有真正提供为什么您应该从抽象类派生而不是接口派生的原因,它听起来更像是为什么首先存在相等比较器接口的原因。它说的实际上是没用的,它基本上描述了默认实现在做什么。如果有的话,他们在这里提供的"推理"听起来更像是一个指南,告诉你的比较器可以做什么,而与它实际做什么无关。

EqualityComparer<T>类的公共/保护接口,只有一个可取之处,它实现了非泛型的IEqualityComparer接口。我认为他们的意思是说他们推荐从它派生,因为EqualityComparer<T>实际上实现了非泛型IEqualityComparer接口,这样你的类就可以在需要非泛型比较器的地方使用。

IComparer<T>的注释部分更有意义:

我们建议您从Comparer<T>类派生而不是实现IComparer<T>接口,因为Comparer<T>类提供了IComparer.Compare方法的显式接口实现和Default属性,该属性获得对象的默认比较器。

我怀疑它应该对IEqualityComparer<T>说一些类似的东西,但是一些想法被混淆了,最终以一个不完整的描述结束。


关于你的第二个问题:

在库中找到的集合的主要目标是尽可能灵活。实现这一点的一种方法是通过提供IComparer<T>IEqualityComparer<T>来进行比较,从而允许自定义比较对象中的对象的方法。在没有提供默认比较器的情况下,获取默认比较器的实例要比直接进行比较容易得多。这些比较器又可以包含调用适当的比较器所需的逻辑。

。默认比较器可以确定T是否实现IEquatable<T>并在对象上调用IEquatable<T>.Equals或使用Object.Equals。在比较器中封装比在集合代码中重复要好。

此外,如果他们想直接调用IEquatable<T>.Equals,他们必须在T上添加一个约束,使这个调用成为可能。这样做会降低它的灵活性,并首先抵消提供比较器的好处。

我不明白这个建议。这在我看来很奇怪。

对于2 -通常,您最终会得到具有IEqualityComparer<T>的类型(例如Dictionary)。虽然实现可以存储空值并显式地调用Equals本身,但这样做将是一件痛苦的事情-并且还会涉及到明显的丑陋,以确保它不会不必要地框住实现IEquatable<T>的值类型。使用EqualityComparer<T>.Default接口明显更简单,更一致。

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

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

Class EqualityComparer使用工厂设计模式。

在工厂模式中,我们创建对象而不向客户端公开创建逻辑,并使用公共接口引用新创建的对象。

的好处是,所有的EqualityComparer的用户只需要调用属性default,所有的事情都为他们做了,以创建适当的对象,公开接口IEqualtiyComparer

这样做的好处是,如果你需要一个IEqualityComparer作为函数的参数,那么你就不必检查类T是否实现了IEqualtiy<T>,字典会为你做这件事。

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

此外,与任何工厂一样,您只需更改工厂的参数,使其产生完全不同的相等比较器。

当然,您可以创建一个不从EqualtyComparer<T>派生的相等比较器工厂,但如果您确实派生了您的工厂,则可以创建一个额外类型的相等比较器:默认相等比较器,它使用IEquatable<T>或Object.Equals。您不必为此编写任何额外的代码,只需派生即可!

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

作为一个例子,假设您想检查两个字典是否相等。我们可以想到几个层次的平等:

  1. 字典X和Y是相等的,如果它们是相同的对象
  2. 如果X和Y具有相等的键(使用字典键比较器),并且它们的值是相同的对象
  3. 如果X和Y有相等的键(使用字典键比较器),如果它们的值相等,则使用TValue
  4. 的默认相等比较器
  5. 如果X和Y具有相等的键(使用字典键比较器),并且使用提供的值相等比较器具有相等的值,则X和Y相等。

如果你的字典比较器类是从EqualityComparer派生的,那么你已经有了comparer(1)。如果提供的TValue比较器是从EqualityComparer派生的,那么(3)和(4)之间没有真正的区别。

那么让我们派生可以创建这四个比较器的工厂:

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

对于comparer(2),您可以使用参考值comparer,如stackoverflow IEqualityComparer中所述,它使用ReferenceEquals

现在我们有四个不同的相等比较器,只提供了一个比较器的代码。其余的被重复使用!

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

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

是否相等
// 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;
}

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

var dictionaryX = ...
var dictionaryY = ...
var valueComparer1 = ...
var valueComparer2 = ...
var equalityComparer1 = DictionaryComparer<...>.Default();
var equalityComparer2 = DictionaryComparer<...>..CreateDefaultValueComparer();
var equalityComparer3 = DictionaryComparer<...>.CreatereferenceValueComparer();
var equalityComparer4 = DictionaryComparer<...>
   .CreateContentComparer(valueCompaerer1);
var equalityComparer5 = DictionaryComparer<...>
   .CreateContentComparer(valueCompaerer2);

所以派生导致相等比较器工厂总是有一个合适的默认比较器。省得我自己写代码

如果你改变MSDN解释中的一个词,即派生自use,这就更有意义了。

On MSDN: EqualityComparer<T>

我们建议您(不是从)使用 EqualityComparer<T>类而不是实现IEqualityComparer<T>接口,因为EqualityComparer<T>类使用IEquatable<T>.Equals方法而不是Object.Equals方法来测试相等性。这与Dictionary类和其他泛型集合的ContainsIndexOfLastIndexOfRemove方法一致。

当然,这只适用于T实现IEquality<T>

请注意,奇怪的是,只有ArrayList<T>IndexOfLastIndexOf方法,并且没有重载为任何方法取IEqualityComparer<T>。其他泛型集合的构造函数接受IEqualityComparer<T>

On MSDN: Comparer<T>:

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

当然,这只适用于T实现IComparableIComparable<T>

如果T没有实现从EqualityComparer<T>Comparer<T>派生的所需接口,它是有用的,因为它免费提供了非泛型接口的实现。

另一方面,实现IEqualityComparer<T>IComparer<T>可以获得性能优势,因为它可以跳过对IEquatable<T>IComparable<T>的调用。