【问题标题】:EqualityComparer<T>.Default vs. T.EqualsEqualityComparer<T>.Default 与 T.Equals
【发布时间】:2011-05-03 16:07:50
【问题描述】:

假设我有一个通用的MyClass&lt;T&gt;,它需要比较两个&lt;T&gt; 类型的对象。通常我会做类似...

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

现在假设我的MyClass&lt;T&gt; 有一个支持传递自定义IEqualityComparer&lt;T&gt; 的构造函数,类似于Dictionary&lt;T&gt;。在那种情况下,我需要做...

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&lt;T&gt;.Default,我可以使用它吗?然后这个 sn-p ...

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

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

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

【问题讨论】:

  • 注意:如果o1 为null,o1.Equals(o2) 将失败并出现无处不在的NullReferenceException,而EqualityComparer&lt;T&gt;.Default.Equals(o1, o2) 将很高兴返回false。
  • @M.Stramm 这就是为什么我说“我会做类似......”,而不是“这是我的生产代码......”;-)

标签: c# .net generics comparison iequalitycomparer


【解决方案1】:

应该是一样的,但不能保证,因为它取决于T类型的实现细节。
说明:
如果没有对T 的约束,o1.Equals(o2) 将调用Object.Equals,即使T 实现IEquatable&lt;T&gt;
但是,EqualityComparer&lt;T&gt;.Default 将仅使用Object.Equals,如果T 没有'不实现IEquatable&lt;T&gt;。如果它确实实现了该接口,则它使用IEquatable&lt;T&gt;.Equals
只要TObject.Equals的实现只是调用IEquatable&lt;T&gt;.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&lt;T&gt;.Default 是一个不错的方法,因为您不需要支持有缺陷的对象!

【讨论】:

  • 任何使用 IEquatable.Equals 和 object.Equals 做不同事情的类型已经注定:)
  • 谢谢。根据我的用例,我什至可以指定像 public MyClass&lt;T&gt; where T : IEquatable&lt;T&gt; 这样的约束。好东西,你们都非常有帮助。
  • 返回语句看起来更像return other != null &amp;&amp; other.ID == ID;
  • 如果覆盖 Equals,别忘了覆盖 GetHashCode - stackoverflow.com/questions/23757210/… Compiler Warning (level 3) CS0659)
  • @MarcGravell 你无法避免它,如果你从一个密封其object.Equals 实现的类继承,比如DependencyObject。在这些情况下,使用EqualityComparer&lt;T&gt;.Default 确保调用您自己的Equals 实现非常重要。
【解决方案2】:

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

EqualityComparer&lt;T&gt;.Default 返回的比较器取决于T。例如,如果T : IEquatable&lt;&gt;,则将创建相应的EqualityComparer&lt;T&gt;

【讨论】:

  • IEquatable 而不是 IComparable
【解决方案3】:

是的,我认为使用EqualityComparer&lt;T&gt;.Default 是明智的,因为如果T 类型实现它,它使用IEquatable&lt;T&gt; 的实现,否则使用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))
    {
     ...
    }
}

【讨论】:

    【解决方案4】:

    您可以使用 null coaelescense 运算符 ??缩短 if 如果它真的很重要

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

    【讨论】:

    • 这如何回答这个问题?
    【解决方案5】:

    这正是Dictionary&lt;&gt; 和 BCL 中的其他泛型集合所做的,如果您在构造对象时没有指定比较器。这样做的好处是EqualityComparer&lt;T&gt;.Default 将为IEquatable&lt;T&gt; 类型、可空类型和枚举返回正确的比较器。如果 T 不是这些,它会像旧代码一样进行简单的 Equals 比较。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-27
      • 2019-05-13
      • 2011-08-08
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多