domingo, 10 de noviembre de 2013

Tipos Anónimos



Los tipos anónimos son una característica introducida dentro del Framework 3.0.

Los tipos anónimos nos ofrecen la posibilidad de crear objetos al vuelo con propiedades de solo lectura, evitando tener que definir de manera explícita una clase para almacenar este tipo de datos equivalentes

Esta característica daba unas posibilidades de dinamismo a los desarrolladores de .Net solo reservada a lenguajes como JavaScript, Python, etc.





Recuerda que aquí tienes el indice de todos los posts del Curso de LinQ.


Los tipos anónimos se definen con la palabra reservada new seguida de llaves, y dentro de estas se coloca un alias y un valor para cada propiedad, por supuesto el compilador inferirá el tipo para crear cada una de ellas:

var persona = new { DNI = "123456789A", Nombre = "Walter", Edad = 54 };

El compilador generará una clase interna, como si la hubiéramos definido de manera explícita antes:















El compilador, le añadirá un nombre de manera interna, en este caso será el siguiente

Código para obtener el nombre:

var persona = new { DNI = "123456789A", Nombre = "Walter", Edad = 54 };

System.Diagnostics.Debug.Print(persona.GetType().Name);

Resultado:


<>f__AnonymousType0`3


Los tipos anónimos tienen una serie de limitaciones:

  • Todas sus propiedades son de solo lectura.
  • No pueden tener métodos como miembros de clase.
  • No pueden tener eventos como miembros de clase.
  • Aparte de todas estas, también comparten las mismas limitaciones que la asignación implícita de tipos (var).

Algo que sí que nos permite, pero que no suele ser demasiado común es añadir llamadas a eventos mediante Expresiones Lambdas:


var persona = new { DNI = "123456789A", Nombre = "Walter", Edad = 54, manejador = new Action<string>((a) => Console.WriteLine(a)) };

persona.manejador("Algo !!!");

Esto es completamente válido y funciona!!!


Comparaciones

Una vez visto la forma y las virtudes y defectos de los tipos anónimos, una duda que nos puede saltar a la cabeza es saber cómo podemos comparar estos objetos, y las reglas que siguen, porque como veremos a continuación, aunque sean clases, no funcionan exactamente de la misma forma.


Tenemos dos opciones para comprar objetos dentro del CLR, una de ellas es el método de la clase object equals() y la otra es la utilización del operador ‘==’. A esto también le podemos añadir la comparación del valor que devuelve el método de object GetHashCode(), que debería ser un valor entero único para cualquier objeto creado en el proceso.  Cuando realizamos esta comparación con tipos por referencia (class) si no se ha realizado ninguna redefinición de ninguno de ellos, por defecto funcionan igual, y es comparando la dirección de memoria a la que apuntan.


Ejemplo para tipos explícitos

Si definimos implícitamente la clase Persona:

public class Persona
{
    public string DNI { get; set; }
    public string Nombre { get; set; }
    public int Edad { get; set; }
}

Y creamos dos objetos que a simple vista parecen iguales, pero no lo son, ya que aunque los valores de sus propiedades son las mismas sus referencias son diferentes, lo que quiere decir que son equivalentes:

Persona persona1 = new Persona { DNI = "123456789A", Nombre = "Walter", Edad = 54 };
Persona persona2 = new Persona { DNI = "123456789A", Nombre = "Walter", Edad = 54 };

Si lanzamos una comprobación general de sus métodos:

Persona persona1 = new Persona { DNI = "123456789A", Nombre = "Walter", Edad = 54 };
Persona persona2 = new Persona { DNI = "123456789A", Nombre = "Walter", Edad = 54 };

Console.WriteLine("HasCode 1 -> {0}", persona1.GetHashCode());
Console.WriteLine("HasCode 2 -> {0}", persona2.GetHashCode());

bool iguales         = persona1.Equals(persona2);
bool igualesOperador = persona1 == persona2;

Console.WriteLine("iguales ?? --> {0}", iguales);
Console.WriteLine("   ==   ?? --> {0}", igualesOperador);
Console.Read();

Resultado:












Como podemos comprobar, sus métodos GetHashCode devuelven valores diferentes y las dos comparaciones devuelven false en ambos casos.


Ejemplo para tipos anónimos

En el caso de los tipos anónimos, veremos que difieren de los tipos explícitos. Par los tipos anónimos el método equals() de la clase object, no compara su referencia directamente, si no que compara los valores de cada una de sus propiedades, por lo que dos objetos equivalentes, si son de tipo anónimo, al aplicar el método equals() entre ellos darán un resultado positivo. También el método GetHashCode() se ha modificado y el entero resultante será el mismo para objetos equivalentes. Por el contrario el operador ‘==’ seguirá realizando comparación entre referencias.

Vamos a verlo en un ejemplo muy parecido al anterior pero con tipos anónimos:

var persona1 = new { DNI = "123456789A", Nombre = "Walter", Edad = 54 };
var persona2 = new { DNI = "123456789A", Nombre = "Walter", Edad = 54 };

Console.WriteLine("HasCode 1 -> {0}", persona1.GetHashCode());
Console.WriteLine("HasCode 2 -> {0}", persona2.GetHashCode());

bool iguales         = persona1.Equals(persona2);
bool igualesOperador = persona1 == persona2;

Console.WriteLine("iguales ?? --> {0}", iguales);
Console.WriteLine("   ==   ?? --> {0}", igualesOperador);
Console.Read();

Resultado:












Si modificamos un poco el ejemplo e igualamos referencias:

var persona1 = new { DNI = "123456789A", Nombre = "Walter", Edad = 54 };
var persona2 = persona1;

El resultado sería el esperado y los dos métodos de comparación darían verdadero:












Importancia para LinQ.


Cuando nos metamos directamente con los métodos extensores de LinQ, veremos más en profundidad lo importante que es para todo su engranaje y más especialmente para su método de proyección Select la existencia de los tipos anónimos.