IEquatable和仅重写Object.Equals()有什么区别?

230
我希望我的Food类能够测试它是否等于另一个Food实例。我稍后将在列表中使用它,并且我想使用它的List.Contains()方法。我应该实现IEquatable<Food>还是只重写Object.Equals()?根据MSDN所述:

此方法通过使用对象的IEquatable.Equals方法的默认相等比较器来确定相等性(T值类型列表的实现)。

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

1
可能是了解IEquatable的重复问题。 - nawfal
4个回答

265
主要原因是性能。当泛型在.NET 2.0中引入时,它们能够添加一系列很棒的类,如List<T>Dictionary<K,V>HashSet<T>等。这些结构在GetHashCodeEquals中大量使用。但对于值类型,这需要装箱操作。IEquatable<T>允许结构实现一个强类型的Equals方法,因此不需要装箱操作。因此,在使用值类型与泛型集合时,性能更好。
引用类型的受益不如值类型那么大,但IEquatable<T>的实现确实可以避免从System.Object进行类型转换,如果频繁调用,这可能会有所差异。
正如Jared Parson的博客中所指出的,您仍然必须实现标准的Object.EqualsObject.GetHashcode重写方法。

10
在C++中这是正确的,但在.NET语言中不是,因为它们强制类型安全。进行运行时强制转换,如果转换失败就会抛出异常。所以进行类型转换需要支付一点运行时代价。编译器可以优化向上转型,例如:object o = (object)"string";但向下转型必须在运行时进行,例如:string s = (string)o; - Josh
9
我建议阅读Jeff Richter的《CLR via C#》和Jon Skeet的《C# in Depth》。至于博客,Wintellect博客、msdn博客等都不错。 - Josh
2
IEquatable<T> 接口除了提醒开发人员在类或结构中包含 public bool Equals(T other) 成员之外,还有其他作用吗?接口的存在与否在运行时没有任何影响。重载 Equals 方法似乎是必要的全部内容。 - mikemay
1
@mikemay,这对于寻找它的类来说是有区别的,通常使用EqualityComparer<T>.Default,这是基本上每个需要默认比较器的BCL类(如HashSet<T>)中的规范默认比较器。 - Ohad Schneider
1
此处所述,如果集合是泛型的,则首先使用IEquatable<T>比较项是否相等,如果未实现,则使用object.Equals() - Hamed
显示剩余2条评论

56
根据MSDN的说法:

如果您实现了IEquatable<T>,则还应该重写基类Object.Equals(Object)GetHashCode方法的实现,以使它们的行为与IEquatable<T>.Equals方法一致。如果您确实重写了Object.Equals(Object),则您重写的实现也会在对您的类上的静态Equals(System.Object,System.Object)方法的调用中被调用。这确保了所有调用Equals方法返回一致的结果。

因此,似乎两者之间没有真正的功能差异,具体取决于类的使用方式可能会调用其中之一。从性能角度来看,最好使用泛型版本,因为它没有相关的装箱/拆箱开销。
从逻辑角度来看,实现接口也更好。覆盖对象并不能告诉任何人您的类实际上是可等同比较的。覆盖可能只是一个什么都不做或浅层次实现。明确地使用接口意味着,“嘿,这个东西可以用于相等性检查!”这是更好的设计。

10
如果结构体将用作字典或类似集合中的键,则一定要实现IEquatable(of theirOwnType),这将提供显著的性能提升。非可继承类通过实现IEquatable(of theirOwnType)可以获得轻微的性能提升。可继承类不应实现IEquatable。 - supercat

36

延续Josh的想法,这里提供一个实际例子。+1给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的优点是什么?这只是风格问题还是真的有优势? - devoured elysium
24
如果出现“obj as EntityBase”的情况-如果obj不属于EntityBase类型,它将传递“null”并继续而不会出现任何错误或异常,但是在“(EntityBase)obj”的情况下,它将强制将obj转换为EntityBase,如果obj不属于EntityBase类型,则会抛出InvalidCastException。是的,“as”只能应用于引用类型。 - this. __curious_geek
我理解答案(相等性通用 - 所以强类型,不需要转换为对象等)。但是我对以下语句有一个问题:>>>这样,我就拥有了可重用的Equals()方法,可以立即适用于所有我的派生类<<< 如果没有实现'Iequatable<T>',只需重写'equals',这也可以适用于派生类型,对吧?我只是想确定你的意图 :) - Dreamer
1
Josh的链接指向Jared Par的博客,似乎暗示你还需要重写GetHashCode。这不是这种情况吗? - Amicable
3
我并没有真正理解你的实现提供的额外价值。你能否澄清一下你的抽象基类解决的问题? - Mert Akcakaya
1
@Amicable - 是的,每当你重写Object.Equals(Object)时,你也必须重写GetHashCode以保证容器可以正常工作。 - namford

2

如果我们调用 object.Equals,那么对值类型进行昂贵的装箱是不可取的。在性能敏感的场景中,这是不可取的。解决方法是使用 IEquatable<T>

public interface IEquatable<T>
{
  bool Equals (T other);
}
IEquatable<T>的理念是它可以比object.Equals更快地提供相同的结果。像以下这样的泛型类型必须使用约束where T : IEquatable<T>
public class Test<T> where T : IEquatable<T>
{
  public bool IsEqual (T a, T b)
  {
    return a.Equals (b); // No boxing with generic T
  }
}

否则,它会绑定到较慢的object.Equals()

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接