【问题标题】:What's the difference between IEquatable and just overriding Object.Equals()?IEquatable 和仅覆盖 Object.Equals() 之间有什么区别?
【发布时间】:2011-02-13 15:54:29
【问题描述】:

我希望我的Food 类能够在它等于Food 的另一个实例时进行测试。稍后我会将它用于列表,并且我想使用它的List.Contains() 方法。我应该实现IEquatable<Food> 还是只覆盖Object.Equals()?来自 MSDN:

此方法通过以下方式确定相等性 使用默认的相等比较器, 由对象的定义 的实施 T 的 IEquatable.Equals 方法 (列表中值的类型)。

所以我的下一个问题是:.NET 框架的哪些函数/类使用Object.Equals()?我应该首先使用它吗?

【问题讨论】:

标签: c# .net equals equality iequatable


【解决方案1】:

主要原因是性能。当在 .NET 2.0 中引入泛型时,它们能够添加一堆整洁的类,例如List<T>Dictionary<K,V>HashSet<T> 等。这些结构大量使用了GetHashCodeEquals。但是对于值类型,这需要装箱。 IEquatable<T> 让结构实现强类型 Equals 方法,因此不需要装箱。因此,将值类型与泛型集合一起使用时性能会更好。

引用类型没有那么大的好处,但 IEquatable<T> 实现确实可以让您避免来自 System.Object 的强制转换,如果频繁调用它会产生影响。

正如 Jared Parson's blog 中所述,您必须仍然实现标准的 Object.EqualsObject.GetHashcode 覆盖。

【讨论】:

  • 引用类型之间是否有强制转换?我一直认为,当您将一些不明显的转换从一种对象分配给另一种对象时,转换只是您对编译器所做的“声明”。也就是说,在你编译之后,代码甚至都不知道那里有演员表。
  • 在 C++ 中确实如此,但在强制类型安全的 .NET 语言中则不然。有一个运行时强制转换,如果强制转换不成功,则会引发异常。所以有一个小的运行时间惩罚来支付强制转换。编译器可以优化掉向上转换,例如 object o = (object)"string";但是向下转换 - 字符串 s = (string)o; - 必须在运行时发生。
  • 我明白了。碰巧你有什么地方可以让我获得关于.NET的那种“更深入”的信息?谢谢!
  • 我会推荐 Jeff Richter 的 C# 和 Jon Skeet 的 C# 深度 CLR。至于博客,Wintellect博客不错,msdn博客等
  • IEquatable<T> 接口除了提醒开发人员在类或结构中包含public bool Equals(T other) 成员之外,还有什么作用?接口的存在与否在运行时没有区别。 Equals 的重载似乎是所有必要的。
【解决方案2】:

根据MSDN

如果你实现IEquatable<T>,你 还应该覆盖基类 的实现 Object.Equals(Object)GetHashCode 使他们的行为是一致的 与IEquatable<T>.Equals 方法。如果你确实覆盖 Object.Equals(Object),你的覆盖 调用中也调用了实现 到您班级的静态Equals(System.Object, System.Object) 方法。 这确保了所有的调用 Equals 方法返回一致 结果。

所以看起来两者之间没有真正的功能差异,除了可以根据类的使用方式调用任何一个。从性能的角度来看,最好使用通用版本,因为没有与之相关的装箱/拆箱损失。

从逻辑的角度来看,实现接口也更好。覆盖该对象并不能真正告诉任何人您的类实际上是平等的。覆盖可能只是一个什么都不做的类或一个浅实现。使用接口明确表示,“嘿,这东西对相等检查有效!”这只是更好的设计。

【讨论】:

  • 结构体如果要用作字典或类似集合中的键,则绝对应该实现 iEquatable(of theirOwnType);它将提供重大的性能提升。通过实现 IEquatable(of theirOwnType),不可继承的类将获得轻微的性能提升。可继承的类应该//不// 实现 IEquatable。
【解决方案3】:

用一个实际的例子来扩展 Josh 所说的话。 +1 to Josh - 我正准备在我的回答中写下同样的内容。

public abstract class EntityBase : IEquatable<EntityBase>
{
    public EntityBase() { }

    #region IEquatable<EntityBase> Members

    public bool Equals(EntityBase other)
    {
        //Generic implementation of equality using reflection on derived class instance.
        return true;
    }

    public override bool Equals(object obj)
    {
        return this.Equals(obj as EntityBase);
    }

    #endregion
}

public class Author : EntityBase
{
    public Author() { }
}

public class Book : EntityBase
{
    public Book() { }
}

这样,我就有了可重复使用的 Equals() 方法,它适用于我的所有派生类。

【讨论】:

  • 再问一个问题。使用“obj as EntityBase”而不是(EntityBase)obj 有什么好处?只是风格问题还是有什么优势?
  • 在“obj as EntityBase”的情况下 - 如果 obj 不是 EntityBase 类型,它将传递“null”并继续没有任何错误或异常,但在“(EntityBase)obj”的情况下,它将强制尝试将 obj 强制转换为 EntityBase,如果 obj 不是 EntityBase 类型,它将抛出 InvalidCastException。是的,“as”只能应用于引用类型。
  • Josh 指向 Jared Par 博客的链接似乎表明您还需要覆盖 GetHashCode。不是这样吗?
  • 我并没有真正得到您的实现提供的额外价值。你能澄清一下你的抽象基类解决的问题吗?
  • @Amicable - 是的,每当您覆盖 Object.Equals(Object) 时,您还必须覆盖 GetHashCode 以便容器工作。
【解决方案4】:

如果我们调用object.Equals,它会强制对值类型进行昂贵的装箱。这在性能敏感的场景中是不可取的。解决方法是使用IEquatable&lt;T&gt;

public interface IEquatable<T>
{
  bool Equals (T other);
}

IEquatable&lt;T&gt; 背后的想法是它提供与object.Equals 相同的结果,但速度更快。约束 where T : IEquatable&lt;T&gt; 必须与下面的泛型类型一起使用。

public class Test<T> where T : IEquatable<T>
{
  public bool IsEqual (T a, T b)
  {
    return a.Equals (b); // No boxing with generic T
  }
}

否则,它会绑定到slower object.Equals()

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-01-25
    • 1970-01-01
    • 2016-06-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-02-23
    • 1970-01-01
    相关资源
    最近更新 更多