miércoles, 21 de mayo de 2014

Tipos Genéricos en C# - Parte 1: Introducción

Tabla de Contenido

0. Introducción
1. ¿Qué es un Tipo Genérico?
1.1 Genéricos en el Framework .NET
1.2 Historia de los tipos genéricos
2. Genéricos y Colecciones
3. Ejemplo Básico de Uso de Tipos Genéricos Personalizados
4. Palabra Clave default
5. Diferencia entre los Genéricos de C# y las Plantillas de C++
6. La CLR y los Tipos Genéricos
6.1 Tipos por valor
6.2 Tipos por referencia
7. Reflection
8. Auto-Referenciación de Definiciones
9. Datos Estáticos
10. Covarianza y Contra-varianza
10.1 Covarianza
10.2 Contra-varianza
11. Notas Finales de Introducción a Tipos Genéricos
12. Conclusiones
13. Glosario
14. Literatura & Enlaces

0. Introducción

Empiezo esta serie artículos de Genéricos en C# introduciendo los conceptos esenciales, algo de historia, y su potencial en la creación de tipos seguros en nuestros modelos; también, haré un breve contraste entre genéricos y herencia: destacando las fortalezas de cada una. A pesar de que ya he tocado este (a modo de práctica) en recetas (Creación de un Tipo de Dato Genérico), sin lugar a dudas no está demás tocar aspectos particulares en lo que se refiere a colecciones (que es donde se destaca su utilidad). ¡Bienvenidos!

1. ¿Qué es un Tipo Genérico?

Podemos empezar con una definición sencilla de Tipo Génerico: un tipo genérico corresponde con un contenedor de tipos, es decir, un lugar u ubicación en donde podemos hacer que resida un tipo de dato específico. Este concepto se puede contrastar con las plantillas [5] en el lenguaje de programación C++. Gráfica podríamos concebirlo de esta manera:
Concepto (visual) de un tipo genérico
Figura 1. Concepto (visual) de un tipo genérico.
El término Colección sólo corresponde con el nombre del contendedor genérico del tipo de datos que deseemos colocar en su interior. Para especificarlo utilizamos un tipo de dato arbitrario: T. (Vamos adelante estudiáremos las ventajas sobre la seguridad de tipos que aporta el uso de genéricos, y además, de la reducción de operaciones de casting y boxing.) La letra T es sólo una convención histórica heredada para significar Template (plantilla, en español).

1.1 Genéricos en el Framework .NET

A través de los genéricos, en el Framework .NET se introdujo el concepto de parámetros de tipos. A través de este concepto ya es posible diseñar clases y métodos que independizan el tratamiento del tipo de dato a manipular (e.g., en una colección operaciones de inserción, remoción, modificación, etc. independiente del tipo pasado como parámetro (parámetro de tipo)). El parámetro de tipo se define en código cliente.

Continuando, en términos generales podemos decir que a través de un tipo genérico, reducimos la operaciones de casting y boxing (cfr. Boxing y Unboxing en C#), ya que la especificación de un tipo para la plantilla -T- incurre en conversiones automáticas.

Veamos en código C# qué es todo esto:
Archivo ListaGenerica.cs:

Hemos creado un tipo (clase) genérico ListaGenerica<T>. Su sintaxis declarativa sigue las reglas básicas de declaración de una clase, sin embargo, hay otros elementos de construcción adecionales: <T>; vemos que los paréntesis angulas rodean el parámetro de tipo: T. Este parámetro en código cliente puede definirse con cualquier otro tipo: intstring, y ClaseEjemplo, por ejemplo.

De manera análoga, el método Agregar de ListaGenerica actúa sobre el parámetro de tipo especificado: Agregar(T entrada). El tipo del argumento entrada corresponde con el parámetro de tipo especificado, por ejemplo, en el código cliente (líneas 32-41) se utilizan los siguientes:
  • int
  • string
  • ClaseEjemplo

1.2 Historia de los tipos genéricos

Los tipos genéricos fueron introducidos en la versión 2.0 (2005-11-07 [8])de Microsoft .NET Framework.

2. Genéricos y Colecciones

De acuerdo con [3], las clases y métodos genéricos combinan los conceptos de reusabilidad, seguridad de tipos (cfr. Seguridad de Tipos en C#), y eficiencia; combinación que no es posible con los tipos particulares (no-genéricos). Debido a que su uso radica, sobretodo, en las colecciones, a partir de la versión 2.0 del Framework .NET en la librería de clases se definió la namespace System.Collections.Generic [9]. En este nombre de espacios residen todos los tipos (clases, interfaces) genéricos de estructuras de datos comunes:
  • Dictionary<TKey, TValue>
  • HashSet<T>
  • LinkedList<T>
  • List<T>
  • Queue<T>
  • SortedDictionary<TKey, TValue>.KeyCollection
  • Stack<T>
  • Entre otros
Es importante mencionar, y de acuerdo con la recomendación dada en [3], que los proyectos de construcción de software que utilicen versiones iguales o superiores a 2.0 del Framework .NET usen los tipos genéricos definidos en System.Collections.Generic, en lugar de los tipos no-genéricos, como: ArrayList [10], Stack [11], Queue [12]. El usar los tipos genéricos, como ya hemos dicho, promueve la reusabilidad, seguridad de tipos, y el desempeño (en la CLR, y en el compilador).

3. Ejemplo Básico de Uso de Tipos Genéricos Personalizados

En el ejemplo de arriba (sección 1.1) apreciamos que es posible crear nuestros propios tipos genéricos usando la notación correspondiente. Desde luego, esto compromete la misma ganancia en desempeño, reusabilidad, y seguridad de tipos que los definidos en la librería de clases del Framework .NET. Veamos un ejemplo más extenso (sin embargo, básico de la versión de la sección 1.1) de creación de un tipo genérico personalizado:


[Nota: Omito comentar el código aquí. El archivo de código fuente anterior contiene descripción detallada de las operaciones.]

> Prueba de ejecución.

Resultado:
Ejecución de prueba ListaGenerica
Figura 1. Ejecución de prueba ListaGenerica.

4. Palabra Clave default

La palabra clave permite determinar el tipo por defecto de un parámetro por tipo. Los valores por defecto que se obtienen a evaluar dependen del contexto. Así:
  • Si el parámetro de tipo es un tipo por referencia, el valor por defecto es null.
  • Si el parámetro de tipo corresponde con un tipo por valor, el valor por defecto es 0.
  • Para la estructuras, el valor por defecto de los miembros depende de la naturaleza del tipo (uno de los dos casos anteriores).
Usando este recurso, podemos resolver uno de los casos generados por el grado de generalización de los tipos genéricos: el no conocer el valor por defecto del argumento de tipo [13].

public void Metodo(T t)
{
T temp = default(t);

if ( t = null || t = 0)
{
return;
}

// resto de la implementación
}

5. Diferencia entre los Genéricos de C# y Plantillas de C++

En abstracto, tanto las plantillas de C++ y como los genéricos de C# contribuyen al principio de la generalización a través de los parámetros de tipo; sin embargo, según [14], los tipos genéricos de C# sólo están concebidos para soluciones que provee el propio lenguaje en la construcción de soluciones bajo el umbral de su concepción. Empero, veamos un resumen de las diferencias principales (fuente original [14]):
  • C# generics do not provide the same amount of flexibility as C++ templates.
    • It is not possible to call arithmetic operators in a C# generic class, although it is possible to call user defined operators.
  • C# does not support explicit specialization; that is, a custom implementation of a template for a specific type.
  • C# does not support partial specialization: a custom implementation for a subset of the type arguments.
  • C# does not allow the type parameter to be used as the base class for the generic class.
[Nota: El listado completo se haya en [14].]

6. La CLR y los Tipos Genéricos

La CLR trata los tipos genéricos de forma diferente dependiendo de si el parámetro de tipo es por valor o por referencia.

6.1 Tipos por valor

Por cada tipo por valor que creemos de una clase genérica, la CLR creará una versión especializada de la clase genérica, por ejemplo:

Lista<short> listaShort1;

crea una versión especializada de la colección Lista para el tipo int. Si creamos otra instancia de este mismo tipo para int, la CLR reusará el tipo especializado de Lista:

Lista<short> listaShort2;

Sin embargo, si hacemos esto:

List<int> listaInt;

la CLR recurrirá a crear otra versión especializada de la clase genérica para el tipo por valor int.

6.2 Tipos por referencia

Para los tipos de referencia la CLR procede de forma distinta. Debido a que los tipos por referencia ocupan el espacio en memoria. Este ejemplo lo refleja:

Tenemos estas dos clases:

class Producto { }

class Canasta { }

Cuando creamos dos instancias de la colección Lista:

List<Producto> listaProductos;

List<Canasta> listaCanasta;

la CLR sólo creará una clase especializada de la lista genérica List<T>.

[Nota: Para obtener más detalle de la operación de la CLR sobre genéricos, recomiendo la lectura de [15].]

7. Reflection

Desde la versión 2.0 de C#, varios miembros (métodos de extensión) fueron agregados a la clase System.Type [16]. Entre ellos los que se muestran a la Figura 2 [17]:
Miembros en System.Type para tipos genéricos
Figura 2. Miembros en System.Type para tipos genéricos [17].

Ejemplo de uso de la propiedd IsGeneryType [18] System.Type:

internal class ClaseGenerica<T> { }

internal class ClaseNoGenerica { }

internal class PropiedadIsGenericType
{
public static void Main()
{
Console.WriteLine(typeof(ClaseGenerica<>).IsGenericType.ToString());
Console.WriteLine(typeof(ClaseNoGenerica).IsGenericType.ToString());
}
}

> Prueba de ejecución.

Resultado:
True
False

[Nota: Para otros datos importantes acerca de Reflection en tipos genéricos, recomiendo una lectura más detenida a [17].]

8. Auto-Referenciación de Definiciones

De acuerdo con [1], en la operación de herencia o implementación de una clase o una interfaz, respectivamente, el parámetro(s) de tipo puede hacer referencia a la clase que implementa o hereda del tipo base. Así:

public interface IEquatable<T>
{
public interface IEquatable<T>
}

public class Subclase : IEquatable<Subclase>
{
// Miembros de declaración
}

9. Datos Estáticos

Para invocar un miembro estático de una clase genérica, seguimos esta sintaxis:

{nombre tipo}<Tipo concreto [, Tipo concreto,...]>.{Miembro estático}

Descripción puntual:
  • {nombre tipo}: Nombre de la clase que contiene miembros estáticos.
  • <Tipo concreto [, Tipo concreto,...]>: Conjunto de los argumentos de tipo concretos.
  • {Miembro estático}: Identificador del miembro estático.
Veamos un ejemplo concreto:

internal class Coleccion<T>
{
public static int Contador;
}

public static void Main()
{
Console.WriteLine(++Coleccion<int>.Contador);
Console.WriteLine(++Coleccion<int>.Contador);
Console.WriteLine(++Coleccion<object>.Contador);
Console.WriteLine(++Coleccion<string>.Contador);
}

¿Cuál es el resultado de las cuatro sentencias en el método Main?

1
2
1
1

10. Covarianza y Contra-varianza

10.1 Covarianza

Si tenemos un tipo de dato A que puede ser convertido (conversión implícita de referencias) a B, entonces el tipo genérico X es covariante si X<A> puede convertirse a X<B>. Verbigracia:

IEmpleado<Mensajero> mensajero = ...;
IEmpleado<Empleado> empleado = mensajero;

10.2 Contra-varianza

Es la operación inversa a la covarianza: Si B se puede convertir a Aentonces el tipo genérico X es covariante si X<B> puede convertirse a X<A>. De acuerdo con [1], esta operación está diseñada para interfaces genéricas, y para lograrlo debemos utilizar la palabra reservada contextual in [18]:

public interface IPushable<in T>
{
void Push(T obj);
}

Esta interfaz genérica la implementamos en la estructura de datos Pila<T>:

public class Pila<in T> : IPushable<T>
{
// Miembros
}


> Ejemplo de ejecución.

11. Notas Finales de Introducción a Tipos Genéricos

En [2] destacan algunos puntos importantes sobre el uso de tipos genéricos:
  • Use tipos genéricos para maximizar la reusabilidad de código, uso de tipos seguros, y ganancia en rendimiento en aplicaciones.
  • Los tipos genéricos destacan en la composición de estructuras de datos (colecciones).
  • La librería de clases del Framework .NET contiene tipos genéricos de funcionalidad verificada: System.Collections.Generic.
  • Como vimos en la sección 3 podemos crear nuestros propios tipos genéricos personalizados (sin embargo, siempre recurra a los definidos en la librería de clases).
  • Los métodos también pueden actuar sobre parámetros de tipo.
  • También podemos utilizar reflection para obtener metadatos de los tipos genéricos en tiempo de ejecución (enfoque dinámico).

12. Conclusiones

Hemos descubierto los esenciales de tipos genéricos en C#. Exploramos la sintaxis declarativa de clases genéricas con la construcción <T>. También vimos que los tipos genéricos son idóneos para las colecciones. Inclusive, aprendimos que siempre que sea posible debemos utilizar los tipos genéricos disponibles en el namespace System.Collections.Generic en lugar de los no-genéricos (ArrayList, Stack, Queue). Los ejemplos mostrados en las secciones 1 y 4, son evidencia del uso de genéricos en C#.

13. Glosario

  • Casting
  • Clases
  • CLR
  • Desempeño
  • Genéricos
  • Generics
  • Interfaces

14. Literatura & Enlaces

[1]: C# 5.0 in a Nutshell by Joseph Albahari and Ben Albahari. Copyright 2012 Joseph Albahari and Ben Albahari, 978-1-449-32010-2.
[2]: Generics (C# Programming Guide) - http://msdn.microsoft.com/en-us/library/512aeb7t.aspx
[3]: Introduction to Generics (C# Programming Guide) - http://msdn.microsoft.com/en-us/library/0x6a29h6.aspx
[4]: Receta No. 2-12 en C#: Creación de un Tipo de Dato Genérico - http://ortizol.blogspot.com/2014/05/receta-no-2-12-en-c-creacion-de-un-tipo.html
[5]: Template (C++), the free encyclopedia - https://en.wikipedia.org/wiki/Template_%28C%2B%2B%29
[6]: Boxing y Unboxing en C# | OrtizOL - Experiencias Construcción Software (xCSw) - http://ortizol.blogspot.com/2014/03/boxing-y-unboxing-en-c.html
[7]: Seguridad de Tipos en C# - http://ortizol.blogspot.com/2013/06/seguridad-de-tipos-en-c.html
[8]: .NET Framework - Wikipedia, the free encyclopedia - http://en.wikipedia.org/wiki/.NET_Framework
[9]: System.Collections.Generic Namespace () - http://msdn.microsoft.com/en-us/library/system.collections.generic.aspx
[10]: ArrayList Class (System.Collections) - http://msdn.microsoft.com/en-us/library/system.collections.arraylist(v=VS.100).aspx
[11]: Stack Class (System.Collections) - http://msdn.microsoft.com/en-us/library/system.collections.stack(v=vs.110).aspx
[12]: Queue Class (System.Collections) - http://msdn.microsoft.com/en-us/library/system.collections.queue(v=vs.110).aspx
[13]: default Keyword in Generic Code (C# Programming Guide) - http://msdn.microsoft.com/en-us/library/xwth0h0d.aspx
[14]: Differences Between C++ Templates and C# Generics (C# Programming Guide) - http://msdn.microsoft.com/en-us/library/c6cyy67b.aspx
[15]: Generics in the Run Time (C# Programming Guide) - http://msdn.microsoft.com/en-us/library/f4a6ta2h.aspx
[16]: Type Class (System) - http://msdn.microsoft.com/en-us/library/system.type.aspx
[17]: Generics and Reflection (C# Programming Guide) - http://msdn.microsoft.com/en-us/library/ms173128.aspx
[18]: in (Generic Modifier) (C# Reference) - http://msdn.microsoft.com/en-us/library/dd469484.aspx


J

No hay comentarios:

Publicar un comentario

Envíe sus comentarios, dudas, sugerencias, críticas. Gracias.