EqualityComparer<T>.Default vs. T.Equals

本文关键字:vs Equals Default gt EqualityComparer lt | 更新日期: 2023-09-27 17:54:46

假设我有一个通用的MyClass<T>,它需要比较两个类型为<T>的对象。通常我会这样做…

void DoSomething(T o1, T o2)
{
  if(o1.Equals(o2))
  {
    ...
  }
}

现在假设我的MyClass<T>有一个支持传递自定义IEqualityComparer<T>的构造函数,类似于Dictionary<T>。在这种情况下,我需要做…

private IEqualityComparer<T> _comparer;
public MyClass() {}
public MyClass(IEqualityComparer<T> comparer)
{
  _comparer = comparer;
}
void DoSomething(T o1, T o2)
{
  if((_comparer != null && _comparer.Equals(o1, o2)) || (o1.Equals(o2)))
  {
    ...
  }
}

为了删除这个冗长的if语句,如果使用常规构造函数,我可以将_comparer默认为'默认比较器',这将是很好的。我搜索了类似typeof(T).GetDefaultComparer()的东西,但没有找到任何类似的东西。

我确实找到了EqualityComparer<T>.Default,我可以使用它吗?然后这段代码是否…

public MyClass()
{
  _comparer = EqualityComparer<T>.Default;
}
void DoSomething(T o1, T o2)
{
  if(_comparer.Equals(o1, o2))
  {
    ...
  }
}

…提供相同的结果使用o1.Equals(o2)所有可能的情况下?

(作为旁注,这是否意味着我还需要为<T>使用任何特殊的通用约束?)

EqualityComparer<T>.Default vs. T.Equals

应该是相同的,但不能保证,因为它取决于类型T的实现细节。
解释:
如果没有对T的约束,01 . equals (o2)将调用Object.Equals,即使T实现了IEquatable<T>
然而,如果T没有实现IEquatable<T>, EqualityComparer<T>.Default将只使用Object.Equals。如果它确实实现了该接口,则使用IEquatable<T>.Equals
只要TObject.Equals的实现只是调用IEquatable<T>.Equals,结果是一样的。但是在下面的例子中,结果就不一样了:

public class MyObject : IEquatable<MyObject>
{
    public int ID {get;set;}
    public string Name {get;set;}
    public override bool Equals(object o)
    {
        var other = o as MyObject;
        return other == null ? false : other.ID == ID;
    }    
    public bool Equals(MyObject o)
    {
        return o.Name == Name;
    } 
}

现在,实现这样的类没有任何意义。但是,如果MyObject的实现者忘记覆盖Object.Equals,您也会遇到同样的问题。

结论:
使用EqualityComparer<T>.Default是一个很好的方法,因为您不需要支持有bug的对象!

默认情况下,Object.Equals(a,b)/a.Equals(b)通过引用进行比较,直到在类中被覆盖。

EqualityComparer<T>.Default返回的比较器取决于T。例如,如果是T : IEquatable<>,那么将创建相应的EqualityComparer<T>

是的,我认为使用EqualityComparer<T>.Default是明智的,因为它使用IEquatable<T>的实现,如果类型T实现它,或者Object.Equals的覆盖。你可以这样做:

private IEqualityComparer<T> _comparer;
public IEqualityComparer<T> Comparer
{
    get { return _comparer?? EqualityComparer<T>.Default;}
    set { _comparer=value;}
}
public MyClass(IEqualityComparer<T> comparer)
{  
    _comparer = comparer;
}
void DoSomething(T o1, T o2)
{  
    if(Comparer.Equals(o1, o2))
    {
     ...
    }
}

您可以使用空对齐操作符??缩短if,如果它真的很重要

  if ((_comparer ?? EqualityComparer<T>.Default).Equals(o1, o2))
  {
  }

如果在构造对象时不指定比较器,那么这正是Dictionary<>和BCL中的其他泛型集合所做的。这样做的好处是,EqualityComparer<T>.Default将为IEquatable<T>类型、可空类型和枚举返回正确的比较器。如果T都不是,它将做一个简单的等号比较,就像你的旧代码所做的那样。