Inferencia de Tipos: Cómo funciona ?

Si hay algo que veo que cuesta que los desarrolladores entiendan o mejor dicho confíen es en la inferencia de tipos que presenta  .net  Framework 3.5. Muchos creen que al usar la palabra var, están  resucitando al costoso tipo Variant de Visual Basic 6.0 o creen que están declarando una variable del tipo Object y que en tiempo de ejecución se resuelve el tipo al que pertenece dicha variable como los hacen los lenguajes dinámicos.


Afortunadamente no es así, la resolución del tipo al que pertenece una variable se resuelve en tiempo de compilación no en tiempo de ejecución.  Vamos a verlo con un ejemplo:



La inferencia de tipos es un proceso por el cual el compilador determina el tipo de una variable local que ha sido declarada sin una declaración explícita de su tipo. El tipo es inferido a partir del valor inicial provisto a la variable. A continuación he declarado una variable llamada “inferredType”, cuyo tipo será resuelto por el compilador C# teniendo en cuenta por supuesto el contenido de la variable. Lo cual revela que para que el algoritmo de inferencia de tipos funcione es necesaria una entrada, que es el contenido de la variable. Si no inicializamos la variable a inferir  tendremos un error de compilación.




La ejecución nos revela que el compilador ha inferido que el contenido de “inferredType” es del tipo System.Int32.



Este resultado sería el mismo que obtendríamos si hubiéramos escrito el siguiente código:



La mejor forma de verificar esto es analizando  el IL que generan ambas implementaciones tanto la inferida como la explícita. Con la ayuda de Reflector el código fuente en C# que se obtiene de analizar el IL de ambos ejecutables es exactamente el mismo.



Ahora bien, analicemos esta situación de error:



Particularmente lo que sucede en este caso, es que el compilador ya infirió que el tipo de la variable “inferredType” es System.Int32. Al asignarle contenido de tipo System.String tenemos un error de conversión de tipos. Me parece importante destacar esta situación ya que conozco a mucha gente que pretende que el compilador analice todas las posibles expresiones en las que interviene la variable y posteriormente generalice al tipo más adecuado para la misma. Pues les tengo malas noticias, el algoritmo de inferencia funciona a partir de la primera evaluación eventual  de una expresión, y esto está bien que sea así ya que la función del compilador es resolver el tipo mediante la reducción de expresiones al tipo atómico más implícito que le permita compilar. En el caso de este ejemplo la expresión analizar es una constante numérica. Si se analizaran todas las expresiones en las que interviene una variable para posteriormente generalizar al mejor tipo, el compilador necesitaría  una instancia privada de SQL Server Analisys Services y tardaría bastante tiempo en generar un ejecutable, lo cual no es la idea.
Ahora si queremos ver el mismo ejemplo con una cadena de texto , el código es el siguiente:


 


Al ejecutarlo obtenemos:



Y cuando analizamos el IL con reflector, conseguimos:



La inferencia en los tipos por valor generaliza al tipo más implícito y optimizado del .net Framework. Como el Framework optimiza la performance para tipos enteros de 32-bit(System.Int32 y System.UInt32) un valor de 0,10 ó 100 que perfectamente podría inferirse como System.Byte se infiere como System.Int32. Incluso se recomienda usar los tipos enteros para contadores(aunque contemos de 0 a 10) y variables enteras de acceso frecuentes, ya que la performance en tiempo de ejecución del tipo entero es preferible al storage en RAM que ahorramos si declaramos variables como System.SByte, System.Byte y System.Int16. De la misma manera, con valores de  punto flotante si declaramos una variable con un valor de 3.14 será inferida al tipo System.Double y no como System.Single(float) que perfectamente la puede contener. La razón es que las operaciones con System.Double son optimizadas por hardware. Sólo se infiere a un tipo no optimizado por el Framework(como System.Int64 ó System.Decimal) si el valor de la variable está fuera del rango de los tipos optimizados.
Si por ejemplo queremos que se infiera el valor 3.14 como float en vez de double, debemos proporcionar cierta evidencia que ayude al compilador a inferirlo como float.


var inferredType= (float)3.14; // casting explícito

var inferredType = 3.14f; // notación sufijo

Entonces, resumiento:




  • La inferencia de tipos no es una solución automágica, el compilador no es psíquico ni el tipo de la variable se resuelve utilizando mecanismos de código dinámico, que afecten la performance en tiempo de ejecución.


  • La inferencia de tipos de resuelve en tiempo de compilación, por lo tanto existe un costo en tiempo de compilación, ese costo  es el tiempo que tarda el algoritmo de inferencia en sintetizar una expresión y resolver el tipo de una variable. 


  • La inferencia de tipos tanto de valor como de referencia es para variables locales de métodos. No se aplica para variables de clase, propiedades, parámetros ni valores de retorno.


  • La inferencia de tipos no es nada más que azúcar sintáctica. Es decir una forma de endulzar y agilizar la manera de declarar variables locales. Realmente se aprecia y se valora  cuando se usa para el desarrollo de pruebas unitarias.


  • El tipo generado por el proceso de inferencia NO es un VARIANT.  Visual Basic  6.0 ha sido discontinuado y es considerado tecnología legacy así que actualícense a Visual Basic.net

Fernik

9 comentarios en “Inferencia de Tipos: Cómo funciona ?”

  1. Fer.
    Que tán útiles son los campos inferidos?
    Por el momento la única utilidad que le veo, es que permite un código portable.
    Es decir, si compilas para una plataforma de 32 bits, te infiere un entero de 32. y lo mismo que para la de 64.

    Pero…
    Es posible que el código resulte más confuso? Que opinás?
    Digo, si tenés
    var name;
    var age;
    Será más claro que: “String name;”, “Int16 age”?

    Saludos.

  2. Para codigo portable olvidalo, ya se encarga el framework de ello.

    El unico motivo real por el que yo veo que se ha añadido la inferencia de tipos es por las clases anonimas, ya que por su naturaleza no tenemos otra manera de trabajar con ellas.

    Estilo:
    var ejemplo = new { Nombre = “David”, Age = 25 };

    Bastante util a la hora de realizar consultas con Linq

  3. Pablo, como dice David, el tema de la portabilidad es responsabilidad del framework, aunque un integer en una plataforma de 32 bits y en una plataforma de 64 bits tiene 32 bits. Con respecto a que la inferencia de tipo agregue confusión al código eso es relativo ya que uno debe nombrar las variables por su semántica, y por la misma semántica debe poder inferir el tipo e datos. Por ejemplo “var age”, me tiene que comunicar que age es un entero,por que la edad es un entero positivo. No es necesario recurrir a “var iAge”. Ahora si por las vueltas de la vida tiene que ser “string age”, ahí si recomendaría un uso explícito de tipos para simplificar la lectura del código.

  4. esta informacion esta del asco no tiene nada de lo que yo nesecito y requiero
    ponganle mas empeño por que asi nadie va a entrar a esta pagina ¡¡¡¡¡¡¡¡¡¡¡QUE ASCO!!!!!!!!!!!!!! DA LA INFORMACION

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *