Mostrando entradas con la etiqueta tipos anónimos. Mostrar todas las entradas
Mostrando entradas con la etiqueta tipos anónimos. Mostrar todas las entradas

jueves, 5 de julio de 2007

Sobre inferencia de tipos

En el post anterior hablé de la inferencia de tipos en las expresiones Lambda, sin embargo esta característica no es exclusiva de estas expresiones y mucho menos del Framework 3.5, existe en el Framework 2 y se encuentra relacionada con los delagados, veamos un ejemplo. List<string> list = new List(); list.Add("Visual Basic Dynamic"); list.Add("IronPython"); list.Add("Ruby"); list.Add("Javascript"); list.ForEach(Console.WriteLine); Este código lo encontré en esta dirección en un muy interesante artículo acerca de iteraciones. Pongamos atención es esta línea list.ForEach(Console.WriteLine); Es justamente donde vemos la inferencia de tipos, pero qué es inferencia de tipos, según el diccionario inferir es deducir algo, bien qué es lo que podemos deducir de esta línea, primero veamos la firma del método ForEach public void ForEach(Action<T> action) Acepta un parámetro del tipo Action<T>, entonces vamos a la definición public delegate void Action<T>(T obj); Es un delegado que acepta un parámetro del tipo T y no retorna nada, por lo tanto la traducción del método ForEach sería un método que acepta un delegado que acepta un parámetro del tipo T y no retorna nada. O sea, podemos pasarle cualquier método que acepta un T (string en este caso) y no retorne nada. Bueno, el método WriteLine de Console justamente acepta string (T para nuestra lista de string) y no retorna nada, por lo tanto el compilador acepta la expresión list.ForEach(Console.WriteLine); Esto es justamente uno de los fundamentos de los delegados, seguramente en poco tiempo voy a escribir algo de eso, por ahora cada vez que vean un método que acepta un delegado del algún tipo miren la definición. Por último como es costumbre vamos a ver el código de ForEach public void ForEach(Action<T> action) { if (action == null) { ThrowHelper.ThrowArgumentNullException(ExceptionArgument.match); } for (int i = 0; i < this._size; i++) { action(this._items[i]); } } Como se puede ver en la línea marcada no se hace más que llamar al método que pasamos como parámetro y que debe aceptar T y no regresar nada, en fin, ahora sabemos un poco más como funciona, lo principal es no recordar sino comprender.

domingo, 1 de julio de 2007

¿Cómo funciona un tipo anónimo?

En un post anterior vimos que el resultado de una expresión LINQ puede ser un tipo anónimo (casi siempre lo es), ahora nos preguntamos si esto no es una especie de variant de VB6 disfrazado y tiraría por tierra toda la promocionada comprobación de tipos de C#, esto no es verdad ya que un tipo anónimo si bien no se define explícitamente existe y por lo tanto no podemos decir que es una cosa y en la siguiente línea decir que es otra alegremente sin que el compilador diga nada, vamos a ver por qué. var persona = new {Nombre="Juan",Apellido="Perez", Edad=18 }; ObjectDumper.Write(persona); Este sencillo código podría ser el resultado de una expresión Lambda que daría lo mismo, el echo es que el resultado es un tipo anónimo, en ningún lado yo definí que existe un tipo que tiene tres atributos sin embargo el object dumper no miente (ya que utiliza reflection para determinar los atributos del tipo que deseamos imprimir) y es más, no podemos poner una línea a continuación diciendo persona="tal cosa" porque el compilador no nos deja, nos dice algo del estilo "no se pude converitir el tipo anónimo a string..." bueno, veamos por qué. Echamos mano del Reflector para variar y descompilamos el código generado. private void Test() { var <>g__initLocal0 = new <>f__AnonymousType0(); <>g__initLocal0.Nombre = "Juan"; <>g__initLocal0.Apellido = "Perez"; <>g__initLocal0.Edad = 0x12; var persona = <>g__initLocal0; ObjectDumper.Write(persona); } Si observamos un poco vemos lo siguiente "<>f__AnonymousType0()" y es justamte nuestro tipo anónimo, es más a continuación vemos como se asignan los valores a las propiedades que nosotros definimos, pero ¿qué pasó?, pasó que el compilador creo que tipo por nosotros, si hacemos click sobre la definición Reflector nos muestra la implementación. [CompilerGenerated, DebuggerDisplay(@"\{ Nombre = {Nombre}, Apellido = {Apellido}, Edad = {Edad} }", Type="")] internal sealed class <>f__AnonymousType0<<>j__AnonymousTypeTypeParameter1, <>j__AnonymousTypeTypeParameter2, <>j__AnonymousTypeTypeParameter3> { // Fields [DebuggerBrowsable(DebuggerBrowsableState.Never)] private <>j__AnonymousTypeTypeParameter1 <>i__AnonymousTypeField4; [DebuggerBrowsable(DebuggerBrowsableState.Never)] private <>j__AnonymousTypeTypeParameter2 <>i__AnonymousTypeField5; [DebuggerBrowsable(DebuggerBrowsableState.Never)] private <>j__AnonymousTypeTypeParameter3 <>i__AnonymousTypeField6; // Methods [DebuggerHidden] public override bool Equals(object value) { var type = value as <>f__AnonymousType0<<>j__AnonymousTypeTypeParameter1, <>j__AnonymousTypeTypeParameter2, <>j__AnonymousTypeTypeParameter3>; return ((((type != null) && object.Equals(this.<>i__AnonymousTypeField4, type.<>i__AnonymousTypeField4)) && object.Equals(this.<>i__AnonymousTypeField5, type.<>i__AnonymousTypeField5)) && object.Equals(this.<>i__AnonymousTypeField6, type.<>i__AnonymousTypeField6)); } [DebuggerHidden] public override int GetHashCode() { int num = -1393883635; num = (-1521134295 * num) + ((this.<>i__AnonymousTypeField4 != null) ? this.<>i__AnonymousTypeField4.GetHashCode() : 0); num = (-1521134295 * num) + ((this.<>i__AnonymousTypeField5 != null) ? this.<>i__AnonymousTypeField5.GetHashCode() : 0); return ((-1521134295 * num) + ((this.<>i__AnonymousTypeField6 != null) ? this.<>i__AnonymousTypeField6.GetHashCode() : 0)); } [DebuggerHidden] public override string ToString() { StringBuilder builder = new StringBuilder(); builder.Append("{ Nombre = "); builder.Append(this.<>i__AnonymousTypeField4); builder.Append(", Apellido = "); builder.Append(this.<>i__AnonymousTypeField5); builder.Append(", Edad = "); builder.Append(this.<>i__AnonymousTypeField6); builder.Append(" }"); return builder.ToString(); } // Properties public <>j__AnonymousTypeTypeParameter2 Apellido { get { return this.<>i__AnonymousTypeField5; } set { this.<>i__AnonymousTypeField5 = value; } } public <>j__AnonymousTypeTypeParameter3 Edad { get { return this.<>i__AnonymousTypeField6; } set { this.<>i__AnonymousTypeField6 = value; } } public <>j__AnonymousTypeTypeParameter1 Nombre { get { return this.<>i__AnonymousTypeField4; } set { this.<>i__AnonymousTypeField4 = value; } } } Como vemos, están los tres atributos que definimos y algunas cosas más, como una sobrecarga del método ToString(), el Equals(), GetHashCode() y un poco de información para el debugger, pero en definitiva es un tipo contante y sonante y es por eso que no podemos hacer lo que querramos con él, simplemente no hace falta definirlo pero existe y el compilador será tan estricto en la comprobación de su uso con con cualquier otro tipo. Ahora me voy a dormir porque es muy tarde, nos leemos.