Los tipos anónimos se derivan directamente de object, por tanto definen los métodos de este tipo elemental, entre los métodos heredados tenemos dos que se utilizan de forma especial a la hora de comparar dos instancias de dos tipos anónimos: Equals y GetHashCode.
El primero servirá para comparar dos instancias de tipos anónimos, la comparación de esas instancias se realizará comprobando solo el contenido de las propiedades de solo lectura (que como sabemos son las únicas que podemos crear con C#). Para realizar esa comparación se utiliza el valor generado por el método GetHashCode en cada una de las propiedades de solo lectura.
En la comprobación de igualdad de dos tipos anónimos siempre se tendrán en cuenta los contenidos de todas las propiedades de esos dos tipos anónimos y dicha comprobación la podemos hacer tanto usando el método Equals como usando el operador de igualdad (==), al menos si estamos comparando dos objetos creados usando el mismo tipo anónimo, ya que si los tipos anónimos usados para crear esas dos instancias que queremos comparar son diferentes, solo podremos usar el método Equals, y siempre devolverá un valor falso.
En el listado 4.8 tenemos algunas definiciones de tipos anónimos y cómo realizar una comparación de igualdad en las instancias creadas, en estos ejemplos el valor devuelto en la comparación siempre será false, ya que ninguna de las tres instancias son exactamente iguales, además de que el tipo anó- nimo usados para crear las variables ta6 y ta7 es diferente al tipo anónimo de la variable ta8, por tanto, para comprar si el contenido de ta6 y ta8 son iguales debemos usar el método Equals, ya que al usar el operador de igualdad produciría un error de compilación indicando que: “El operador '==' no se
puede aplicar a operandos del tipo 'AnonymousType#1' y 'AnonymousType#2'”.
var ta6 = new { ID = "003", Nombre = "C#" };
var ta7 = new { ID = "003", Nombre = "Visual C#" };
// La comparación de igualdad // la podemos hacer de dos formas
// (siempre que sean instancias del mismo tipo anónimo)
var sonIguales = ta6.Equals(ta7);
Console.WriteLine(sonIguales);
sonIguales = (ta6 == ta7);
Console.WriteLine(sonIguales);
var ta8 = new { ID = 3, Nombre = "C#" };
// esto dará error
// sonIguales = (ta6 == ta8);
sonIguales = ta6.Equals(ta8);
Console.WriteLine(sonIguales);
Listado 4.8. Comparar instancias de tipos anónimos
Al ver ese error, es posible que nos parezca que no se puede usar el operador de igualdad porque son tipos anónimos diferentes, pero no es así, ya que las tres variables son del mismo tipo anónimo, lo que ocurre es que las instancias tienen diferentes tipos de datos para las propiedades, ya que en el fondo, los tipos anónimos son clases generic.
Usando seudocódigo de C#, el tipo anónimo se definiría tal como vemos en el listado 4.9. Como vemos, es un tipo generic y el constructor recibe como parámetros los valores a asignar a las propie- dades de solo lectura, los tipos de datos de esos parámetros serán de los tipos de datos que indiquemos en el constructor del tipo anónimo, por tanto, aunque en realidad la clase sea la misma, al instanciar los objetos lo haremos usando los tipos de datos que se indiquen en la creación de cada una de esas variables. Sabiendo esto, en el seudocódigo del listado 4.10 vemos que los tipos de datos usados en las variables ta6 y ta7 son <string, string>, mientras que la variable ta8 se crea usando <int, string>, por tanto, las instancias creadas en memoria manejan tipos de datos diferentes en las propiedades de ese tipo seudo-anónimo, de ahí que no podamos comparar con el operador de igualdad dos instancias del supuestamente mismo tipo anónimo. Aclarar que ese error al comparar dos tipos generic que usan tipos de datos diferentes en las propiedades es algo que no es exclusivo de los tipos anónimos, ya que si definimos el tipo de datos del listado 4.9 y lo usamos tal como vemos en el listado 4.10, tampoco permitirá comparar con el operador == las variables ta6 y ta8.
internal sealed class f__AnonymousType0<T0_ID, T1_Nombre> {
// Los campos privados y las propiedades
private readonly T0_ID m_ID;
private readonly T1_Nombre m_Nombre; public T0_ID ID
{
get { return m_ID; } }
public T1_Nombre Nombre {
get { return m_Nombre; } }
// El constructor
public f__AnonymousType0(T0_ID ID, T1_Nombre Nombre) {
this.m_ID = ID;
this.m_Nombre = Nombre; }
// Los métodos
// Redefine ToString, Equals y GetHashCode
}
Listado 4.9. Seudocódigo del tipo anónimo usado en el listado 4.8
f__AnonymousType0<string, string> ta6;
ta6 = new f__AnonymousType0<string, string>("003", "C#");
f__AnonymousType0<string, string> ta7;
ta7 = new f__AnonymousType0<string, string>("003", "Visual C#");
f__AnonymousType0<int, string> ta8;
ta8 = new f__AnonymousType0<int, string>(3, "C#");
Listado 4.10. Seudocódigo que usa el tipo definido en el listado 4.9
Como vemos, el compilador usará el mismo tipo anónimo siempre que las propiedades tengan los mismos nombres, pero las instancias finales dependerán del tipo de datos que usemos al crearlos.
¿Son iguales dos instancias del mismo tipo anónimo con los mismos datos?
Al hacer las comparaciones con instancias de los mismos tipos anónimos, hay que tener en cuenta que si el contenido de las propiedades es exactamente el mismo, el uso del método Equals producirá un resultado diferente al operador de igualdad. Esto es así porque en realidad al comparar tipos por refe- rencia (los tipos anónimos siempre son tipos por referencia), el operador de igualdad comprueba si las dos instancias son la misma, mientras que la sobrecarga del método Equals comprueba el conte- nido de cada una de las propiedades de los dos tipos anónimos a comparar.
El código del listado 4.11 demuestra esto que acabo de comentar. var ta6 = new { ID = "003", Nombre = "C#" };
var ta7 = new { ID = "003", Nombre = "C#" };
// Equals comprueba los contenidos de las propiedades
var sonIguales = ta6.Equals(ta7);
Console.WriteLine("Usando Equals: {0}", sonIguales);
// El operador == comprueba si las dos instancias son iguales
sonIguales = (ta6 == ta7);
Console.WriteLine("Usando ==: {0}", sonIguales);
Listado 4.11. Comprobar el contenido de dos instancias de tipos anónimos
Independientemente de todo lo que el compilador haga para que podamos usar los tipos anónimos, no cabe duda de que en muchas situaciones nos resultarán de utilidad, particularmente cuando los utili- cemos con todo lo relacionado con LINQ.
Pero aún nos quedan otros detalles que debemos conocer sobre los tipos anónimos, tanto para sacarles el máximo rendimiento, como para decidir si utilizar este tipo de clases o usar clases definidas por nosotros.