T 。包含struct和class的行为方式不同

这是一个后续问题: List .Contains和T []。包含不同的行为

T[].ContainsT是类和结构时, T[].Contains的行为不同。 假设我有这个结构

 public struct Animal : IEquatable { public string Name { get; set; } public bool Equals(Animal other) //<- he is the man { return Name == other.Name; } public override bool Equals(object obj) { return Equals((Animal)obj); } public override int GetHashCode() { return Name == null ? 0 : Name.GetHashCode(); } } var animals = new[] { new Animal { Name = "Fred" } }; animals.Contains(new Animal { Name = "Fred" }); // calls Equals(Animal) 

在这里,正如我预期的那样,正确调用通用Equals

但是在课堂上

 public class Animal : IEquatable { public string Name { get; set; } public bool Equals(Animal other) { return Name == other.Name; } public override bool Equals(object obj) //<- he is the man { return Equals((Animal)obj); } public override int GetHashCode() { return Name == null ? 0 : Name.GetHashCode(); } } var animals = new[] { new Animal { Name = "Fred" } }; animals.Contains(new Animal { Name = "Fred" }); // calls Equals(object) 

调用非genericsEquals ,剥夺了实现`IEquatable的好处。

为什么对于struct[]class[] ,数组调用Equals不同, 即使这两个集合看起来都是通用的

arrays怪异是如此令人沮丧,我想完全避免它…

注意:仅当struct 实现IEquatable时才调用Equals的通用版本 如果类型没有实现IEquatable ,则调用Equals非generics重载,无论它是class还是struct

看起来它实际上并不是最终被调用的Array.IndexOf()。 查看源代码,如果是这种情况,我希望在两种情况下都能调用Equals(object)。 通过查看调用Equals的位置处的堆栈跟踪,可以更清楚地了解为什么要获得您所看到的行为(值类型获得Equals(Animal),但引用类型获得Equals(object)。

这是值类型的堆栈跟踪(struct Animal)

 at Animal.Equals(Animal other) at System.Collections.Generic.GenericEqualityComparer`1.IndexOf(T[] array, T value, Int32 startIndex, Int32 count) at System.Array.IndexOf[T](T[] array, T value, Int32 startIndex, Int32 count) at System.Array.IndexOf[T](T[] array, T value) at System.SZArrayHelper.Contains[T](T value) at System.Linq.Enumerable.Contains[TSource](IEnumerable`1 source, TSource value) 

这是引用类型的堆栈跟踪(对象Animal)

 at Animal.Equals(Object obj) at System.Collections.Generic.ObjectEqualityComparer`1.IndexOf(T[] array, T value, Int32 startIndex, Int32 count) at System.Array.IndexOf[T](T[] array, T value, Int32 startIndex, Int32 count) at System.Array.IndexOf[T](T[] array, T value) at System.SZArrayHelper.Contains[T](T value) at System.Linq.Enumerable.Contains[TSource](IEnumerable`1 source, TSource value) 

从这里你可以看到它不是被调用的Array.IndexOf – 它是Array.IndexOf [T]。 该方法最终使用Equality比较器。 在引用类型的情况下,它使用调用Equals(object)的ObjectEqualityComparer。 在值类型的情况下,它使用GenericEqualityComparer调用Equals(Animal),可能是为了避免昂贵的装箱。

如果你在http://www.dotnetframework.org上查看IEnumerable的源代码,它在顶部有一个有趣的位:

 // Note that T[] : IList, and we want to ensure that if you use // IList, we ensure a YourValueType[] can be used // without jitting. Hence the TypeDependencyAttribute on SZArrayHelper. // This is a special hack internally though - see VM\compile.cpp. // The same attribute is on IList and ICollection. [TypeDependencyAttribute("System.SZArrayHelper")] 

我不熟悉TypeDependencyAttribute,但是从评论中,我想知道是否有一些特殊的魔法用于Array。 这可以解释IndexOf [T]如何通过Array的IList.Contains被调用而不是IndexOf。

我认为这是因为他们都使用自己的Equals基础实现

类inheritance实现标识相等性的Object.EqualsStructsinheritance实现值相等的ValueType.Equals

IEquatable的主要目的是允许与通用结构类型进行合理有效的相等比较。 IEquatable.Equals((T)x)行为应该与Equals((object)(T)x);完全相同Equals((object)(T)x); 除了如果T是值类型,前者将避免后者需要的堆分配。 虽然IEquatable不会将T限制为结构类型,并且密封类在某些情况下可能会因使用它而获得轻微的性能优势,但类类型无法从结构类型获得与该接口相同的好处。 如果外部代码使用IEquatable.Equals(T)而不是Equals(Object) ,则正确编写的类可能会执行得稍快,但是否则不应该关心使用哪种比较方法。 因为使用IEquatable与类的性能优势永远不会很大,所以知道它使用类类型的代码可能会决定检查类型是否恰好实现IEquatable所需的时间可能不会被任何界面可以提供的性能提升。

顺便提一下,值得注意的是,如果X和Y是“正常”类,如果X或Y来自另一个,则X.Equals(Y)可以合法地为真。 此外,未密封的类类型的变量可以合法地比较等于任何接口类型之一,无论该类是否实现该接口。 相比之下,结构只能比较自己类型的变量, ObjectValueType或结构本身实现的接口。 类类型实例可能与更广泛的变量类型“相等”的事实意味着IEquatable不像结构类型那样适用于它们。

PS – arrays是特殊的另一个原因是:它们支持一种类不可能的协方差。 特定

 Dog Fido = new Dog(); Cat Felix = new Cat(); Animal[] meows = new Cat[]{Felix}; 

测试meows.Contains(Fido)是完全合法的。 meows.Contains(Fido) 。 如果将meows替换为Animal[]Dog[]的实例,则新数组可能确实包含Fido ; 即使它不是,人们可能合法地拥有一些未知类型的Animal的变量,并想知道它是否包含在meows内。 即使Cat实现IEquatable ,尝试使用IEquatable.Equals(Cat)方法来测试一个IEquatable.Equals(Cat)元素是否等于Fido会失败,因为Fido无法转换为Cat 。 可能有一些方法可以让系统在可行时使用IEquatableIEquatable用时使用Equals(Object) ,但会增加很多复杂性,如果没有超出性能成本的话,很难做到简单地使用Equals(Object)