T . Contiene estructura y clase de comportamiento diferente.

Esta es una pregunta de seguimiento para esto: Lista .Contains y T []. Contiene comportarse de manera diferente

T[].Contains se comporta de manera diferente cuando T es class y struct. Supongamos que tengo esta estructura :

 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) 

Aquí, los generics Equals se llaman correctamente como esperaba.

Pero en el caso de una clase :

 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) 

Se llama Equals no genérico , eliminando el beneficio de implementar `IEquatable.

¿Por qué la matriz de llamadas es Equals a struct[] y class[] , a pesar de que ambas colecciones parecen tener un aspecto genérico ?

La rareza de la matriz es tan frustrante que estoy pensando en evitarla por completo …

Nota: La versión genérica de Equals se llama solo cuando la estructura implementa IEquatable . Si el tipo no implementa IEquatable , la sobrecarga no genérica de Equals se llama independientemente de si es class o struct .

Parece que en realidad no es Array.IndexOf () lo que termina siendo llamado. En cuanto a la fuente de eso, habría esperado que se llamara a Equals (objeto) en ambos casos si ese fuera el caso. Al observar la traza de la stack en el punto donde se llama a Equals, se aclara más el motivo por el que se está viendo el comportamiento (el tipo de valor obtiene Equals (Animal), pero el tipo de referencia obtiene Equals (objeto).

Aquí está la traza de stack para el tipo de valor (estructura 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) 

Aquí está la traza de la stack para el tipo de referencia (objeto 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) 

De esto se puede ver que no se llama Array.IndexOf, se trata de Array.IndexOf [T]. Ese método termina utilizando comparadores de igualdad. En el caso del tipo de referencia, usa ObjectEqualityComparer que llama a Equals (objeto). En el caso del tipo de valor, utiliza GenericEqualityComparer, que llama a Equals (Animal), presumiblemente para evitar un boxeo costoso.

Si observa el código fuente de IEnumerable en http://www.dotnetframework.org , tiene este bit interesante en la parte superior:

 // 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")] 

No estoy familiarizado con TypeDependencyAttribute, pero por el comentario, me pregunto si hay algo de magia en marcha que sea especial para Array. Esto puede explicar cómo IndexOf [T] termina siendo llamado en lugar de IndexOf a través de IList.Contains de Array.

Creo que es porque ambos están usando su propia implementación base de Equals

Las clases heredan Object.Equals que implementa igualdad de identidad, Structs heredan ValueType.Equals que implementa igualdad de valor.

El propósito principal de IEquatable es permitir comparaciones de igualdad razonablemente eficientes con tipos de estructura generics. Se pretende que IEquatable.Equals((T)x) se comporte exactamente igual a Equals((object)(T)x); excepto que si T es un tipo de valor, el primero evitará una asignación de stack que será necesaria para el segundo. Aunque IEquatable no obliga a T a ser un tipo de estructura, y las clases selladas pueden en algunos casos recibir un ligero beneficio de rendimiento al usarlo, los tipos de clase no pueden recibir tanto beneficio de esa interfaz como los tipos de estructura. Una clase escrita correctamente puede tener un rendimiento ligeramente más rápido si el código externo utiliza IEquatable.Equals(T) lugar de Equals(Object) , pero de lo contrario no debería importar qué método de comparación se usa. Debido a que la ventaja de rendimiento de usar IEquatable con clases nunca es muy grande, el código que sabe que está usando un tipo de clase podría decidir que el tiempo requerido para verificar si el tipo que se implementa para implementar IEquatable probablemente no se recuperará. rendimiento que la interfaz podría ofrecer plausiblemente.

Por cierto, vale la pena señalar que si X e Y son clases “normales”, X.Equals (Y) pueden ser legítimamente verdaderas si X o Y se derivan de la otra. Además, una variable de un tipo de clase sin sellar puede compararse legítimamente igual a uno de cualquier tipo de interfaz, ya sea que la clase implemente esa interfaz o no. En comparación, una estructura solo puede compararse igual a una variable de su propio tipo, Object , ValueType o una interfaz que la estructura implementa. El hecho de que las instancias de tipo de clase puedan ser “iguales” a un rango mucho más amplio de tipos de variables significa que el IEquatable no es tan aplicable con ellas como con los tipos de estructura.

PD: hay otra razón por la que las matrices son especiales: admiten un estilo de covarianza que las clases no pueden. Dado

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

es perfectamente legal probar meows.Contains(Fido) . Si los meows se reemplazaran con una instancia de Animal[] o Dog[] , la nueva matriz podría contener Fido ; incluso si no lo fuera, uno podría tener legítimamente una variable de algún tipo desconocido de Animal y desear saber si está contenido dentro de los meows . Incluso si Cat implementa IEquatable , intenta utilizar el IEquatable.Equals(Cat) para probar si un elemento de meows es igual a Fido fallaría porque Fido no se puede convertir en un Cat . Puede haber formas para que el sistema use IEquatable cuando sea viable e Equals(Object) cuando no lo sea, pero agregaría mucha complejidad y sería difícil hacerlo sin un costo de rendimiento que excediera el de simplemente usar Equals(Object) .