2012-08-25 14 views
43

Al igual que en cuestión, me pregunto por qué los diseñadores del lenguaje deben implementar Equals en tipos anónimos que se comportan como tipo de valor. ¿No es engañoso?¿Por qué tipos anónimos equivale a la implementación compara campos?

class Person 
    { 
     public string Name { get; set; } 
     public int Age { get; set; } 
    } 

    public static void ProofThatAnonymousTypesEqualsComparesBackingFields() 
    { 
     var personOne = new { Name = "Paweł", Age = 18 }; 
     var personTwo = new { Name = "Paweł", Age = 18 }; 

     Console.WriteLine(personOne == personTwo); // false 
     Console.WriteLine(personOne.Equals(personTwo)); // true 
     Console.WriteLine(Object.ReferenceEquals(personOne, personTwo)); // false 

     var personaOne = new Person { Name = "Paweł", Age = 11 }; 
     var personaTwo = new Person { Name = "Paweł", Age = 11 }; 
     Console.WriteLine(personaOne == personaTwo); // false 
     Console.WriteLine(personaOne.Equals(personaTwo)); // false 
     Console.WriteLine(Object.ReferenceEquals(personaOne, personaTwo)); // false 
    } 

A primera vista, todos los valores booleanos impresos deben ser falsos. Pero las líneas con llamadas Iguales devuelven valores diferentes cuando se utiliza el tipo Persona y se utiliza el tipo anónimo.

+1

Esta es una de las pocas publicaciones en SO que tiene 3 o más respuestas donde el representante promedio. de responder chicos ha terminado ** 95k **, desde mayo-23-2017. – dotNET

Respuesta

50

Las instancias de tipo anónimo son valores de datos inmutables sin comportamiento o identidad. No tiene mucho sentido hacer referencia, compararlos. En ese contexto, creo que es completamente razonable generar comparaciones estructurales de igualdad para ellos.

Si desea cambiar el comportamiento de comparación a algo personalizado (comparación de referencia o insensibilidad a mayúsculas y minúsculas) puede usar Resharper para convertir el tipo anónimo a una clase con nombre. Resharper también puede generar miembros de igualdad.

También hay una razón muy práctica para hacer esto: Los tipos anónimos son convenientes para utilizar como teclas hash en uniones y agrupaciones LINQ. Por esa razón requieren implementaciones Equals y GetHashCode semánticamente correctas.

+0

Todavía estoy confundido ¿por qué para los tipos anónimos == no se comparan valores como .Equals? –

+10

Porque == no invoca Equals en C# (¡nunca!). Invoca 'operator ==' y los tipos anónimos no tienen ese. Entonces C# usa igualdad de referencia. ¿Eso es bueno o malo? Quién sabe porque es una compensación. Después de todo, nunca sentí la necesidad de comparar dos instancias de tipo anónimo. Nunca sucede por alguna razón. – usr

+1

Estoy de acuerdo en que es inteligente desde una perspectiva de conveniencia (GroupBy() etc), pero un poco engañosa desde una perspectiva técnica. MSDN dice "Los tipos anónimos son tipos de clase que se derivan directamente del objeto ...". Sin embargo, el objeto no implementa IStructuralEquatable, por lo que la interfaz se inyecta de alguna manera detrás de las cortinas, lo que hace que las cosas sean un poco confusas (aunque útiles) ... – Anders

31

Para el qué parte debe pedir a los diseñadores del lenguaje ...

Pero encontré esto en el artículo de Eric Lippert sobre Anonymous Types Unify Within An Assembly, Part Two

Una tipo anónimo le da un lugar conveniente para almacenar una pequeña un conjunto inmutable de pares de nombre/valor, pero te da más que eso. Es también le da una implementación de Equals, GetHashCode y, la mayoría relacionada con esta discusión, ToString. (*)

Cuando el qué parte se presenta en la nota:

(*) Le damos iguales y GetHashCode para que pueda utilizar instancias de tipos anónimos en las consultas LINQ como claves sobre los cuales para realizar se une. LINQ to Objects implementa join utilizando una tabla hash para razones de rendimiento, y por lo tanto necesitamos implementaciones correctas de Equals y GetHashCode.

12

La respuesta oficial de la C# Language Specification (obtenible here):

Los métodos equals y GetHashCode en tipos anónimos anulan los métodos heredados de objeto, y se definen en términos de los Iguales y GetHashCode de las propiedades, de modo que dos instancias del mismo tipo anónimo son iguales si y solo si todas sus propiedades son iguales a.

(el subrayado es mío)

Las otras respuestas explican por qué se hace esto.

Vale la pena señalar que en VB.Net la implementation is different:

una instancia de un tipo anónimo que no tiene propiedades clave es igual sólo a sí mismo.

Las propiedades clave se deben indicar explícitamente al crear un objeto de tipo anónimo. El valor predeterminado es: sin clave, lo que puede ser muy confuso para los usuarios de C#.

Estos objetos no son iguales en VB, pero estaría en código C# -equivalente:

Dim prod1 = New With {.Name = "paperclips", .Price = 1.29} 
Dim prod2 = New With {.Name = "paperclips", .Price = 1.29} 

Estos objetos se evalúan como "igual":

Dim prod3 = New With {Key .Name = "paperclips", .Price = 1.29} 
Dim prod4 = New With {Key .Name = "paperclips", .Price = 2.00} 
8

porque nos da algo que es útil. Considere lo siguiente:

var countSameName = from p in PersonInfoStore 
    group p.Id by new {p.FirstName, p.SecondName} into grp 
    select new{grp.Key.FirstName, grp.Key.SecondName, grp.Count()}; 

Las obras porque la implementación de Equals() y GetHashCode() para los tipos anónimos funciona sobre la base de la igualdad de campo por campo.

  1. Esto significa lo anterior estará más cerca de la misma consulta cuando se ejecuta contra al PersonInfoStore que no se LINQ a objetos. (Todavía no es lo mismo, coincidirá con lo que hará una fuente XML, pero no con lo que resultaría en la mayoría de las intercalaciones de bases de datos).
  2. Esto significa que no tenemos que definir IEqualityComparer para cada llamada a GroupBy que agruparía muy difícilmente con objetos anónimos - es posible pero no es fácil definir un IEqualityComparer para objetos anónimos - y lejos del significado más natural .
  3. Sobre todo, no causa problemas en la mayoría de los casos.

El tercer punto vale la pena examinar.

Cuando definimos un tipo de valor, es natural que quieren un concepto basado en el valor de la igualdad. Si bien es posible que tengamos una idea diferente de la igualdad basada en el valor que la predeterminada, como la coincidencia de un campo dado con las mayúsculas y minúsculas, el valor predeterminado es lógico (si el rendimiento es deficiente y no funciona bien en un caso *). (Además, la igualdad de referencia no tiene sentido en este caso).

Cuando definimos un tipo de referencia, que puede o no querer un concepto basado en el valor de la igualdad. El valor predeterminado nos da igualdad de referencia, pero podemos cambiar eso fácilmente. Si hacemos cambiarlo, podemos cambiarlo por sólo Equals y GetHashCode o para ellos y también ==.

Cuando definimos un tipo anónimo, oh espera, no nos definimos, que es lo que significa anónimos! La mayoría de los escenarios en los que nos preocupamos por la igualdad de referencia ya no existen. Si vamos a tener un objeto alrededor el tiempo suficiente para luego preguntarnos si es el mismo que otro, probablemente no estemos lidiando con un objeto anónimo. Los casos en los que nos importa la igualdad basada en el valor surgen mucho. Muy a menudo con LINQ (GroupBy como vimos más arriba, sino también Distinct, Union, GroupJoin, Intersect, SequenceEqual, ToDictionary y ToLookup) y, a menudo con otros usos (que no es como que no estábamos haciendo las cosas LINQ hace por nosotros con enumerables en 2.0 y hasta cierto punto antes de eso, cualquiera que codifique en 2.0 habría escrito la mitad de los métodos en Enumerable ellos mismos).

En general, ganamos mucho gracias a la igualdad de las clases anónimas.

En la posibilidad de que alguien realmente quiera igualdad de referencia, == usando la igualdad de referencia significa que todavía tienen eso, así que no perdemos nada. Es el camino a seguir.

* La implementación predeterminada de Equals() y GetHashCode() tiene una optimización que permite usar una coincidencia binaria en los casos en que sea seguro hacerlo. Desafortunadamente, hay un error que a veces hace que algunos casos se identifiquen erróneamente como seguros para este enfoque más rápido cuando no lo están (o al menos solían hacerlo, tal vez lo arreglaron). Un caso común es que si tiene un campo decimal, en una estructura, entonces considerará algunas instancias con campos equivalentes como desiguales.

+1

Sin esta implementación de igualdad, la agrupación no podría hacerse en tipos anónimos. –

+0

@PeterRitchie ¿no es eso lo que acabo de decir? –

Cuestiones relacionadas