miércoles, 21 de mayo de 2014

Tipos Genéricos en C# - Parte 3: Parámetros de Tipo

Tabla de Contenido

0. Introducción
1. Concepto Amplio de Parámetro de Tipo
1.1 Determinación del parámetro de tipo
2. Restricción en Parámetros de Tipo
2.1 Múltiples restricciones
2.2 Restricción new()
3. Pautas para la Nomenclatura de Parámetros de Tipo
3.1 Nombres descriptivos
3.2 Nombres cortos
3.3 Prefijo T
4. Conclusiones
5. Glosario
6. Literatura & Enlaces

0. Introducción

Este es el tercer artículo de la serie de tipos genéricos en C#, y en este nos vamos a adentrar en los detalles de los parámetros de tipo: concepto amplio, parámetros de tipo como receptáculos de tipos concretos, convenciones en el nombramiento de los parámetros de tipo. Atravesaremos cada tema de los sub-temas anteriores con ejemplos prácticos para comprender a fondo su naturaleza, propósito y utilidad.

1. Concepto Amplio de Parámetro de Tipo

Hemos visto en los dos artículos anteriores (cfr. Introducción y Beneficios) varios ejemplos de tipos genéricos. En ellos hemos conocido las propiedades de generalización que se alcanza a través de una lista genérica, la cual admite el tratamiento de cualquier tipo de dato: tipos numéricos integrados, tipos de la bibliotecas de clases del Framework .NET, y los propios tipos creados por el programador.

Continuando, a través de la generalización establecemos un receptáculo de tipos que, además, podemos concebir como un contenedor universal de tipos compatibles con esta unidad (dado que podemos restringir a través de la especificación de restricciones sobre los argumentos (parámetros) de tipo). En la Figura 1 se muestra el concepto visual de esta estructura semántica:
Parámetro de tipo como receptáculo o contenedor de tipos específicos
Figura 1. Parámetro de tipo como receptáculo o contenedor de tipos específicos.
En concreto, la lista de uno o más parámetros de tipo T [,T...] corresponde con el receptáculo o contenedor para tipos particulares. Por ejemplo:

List<T>

Soporta el tratamiento de cualquier tipo de dato en su receptáculo <T>, verbigracia:

List<int>
List<string>
List<double>
List

Aunque parezca muy evidente para algunos, las siguientes operaciones no están permitidas:

List<T> lista = new List<T>();

List<> lista = new List<>();

En el primer caso, T no corresponde con ningún tipo de dato definido: se generará el error CS0246 [4]. Por otro lado, el segundo corresponde con un error de sintaxis, errores: CS1525 [5], CS1002 [6], y CS1031 [7].

1.1 Determinación del parámetro de tipo

De acuerdo con [2], cuando tenemos las declaraciones concretas para la clase genérica ListaGenerica<T>:

ListaGenerica<int> listaEnteros = new ListaGenerica<int>();

// Lista con cadenas de caracteres (string):
ListaGenerica<string> listaStrings = new ListaGenerica<string>();

// Lista con tipo ClaseEjemplo:
ListaGenerica<ClaseEjemplo> listaClaseEjemplo = new ListaGenerica<ClaseEjemplo>();

en tiempo de ejecución, la CLR se encarga de substituir cada ocurrencia de T por los tipos int, string, y ClaseEjemplo. Con esta substitución, lo que estamos alcanzado varios de los beneficios inherentes de los tipos genéricos: eficiencia (alto desempeño), y seguridad de tipos. Todo en tiempo de compilación. Lo que se traduce en un programa robusto y fiable para el usuario.

2. Restricción en Parámetros de Tipo

C# cuenta con la cláusula where [7] con la cual podemos restringir los tipos que podemos especificar para el contenedor o receptáculo (o como dicen en [8], argumento de tipo genérico) del tipo genérico. Por ejemplo:

public class ClaseGenerica<T> where T:A
{
// Cuerpo de declaración
}

Declaraciones e instanciamientos permitidos:

ClaseGenerica<A> a = new ClaseGenerica<A>();

ClaseGenerica<B> b = new ClaseGenerica<B>();

ClaseGenerica<C> c = new ClaseGenerica<C>();

Esta es la jerarquía de herencia:

Archivo ClaseGenerica.cs:

> Prueba de ejecución.

La compilación y ejecución son correctas.

Por lo contrario, cuando hacemos algo como esto:

ClaseGenerica<D> d = new ClaseGenerica<D>();

En tiempo de compilación se generará el error CS0311 [9]:


error CS0311: The type `Articulos.Cap03.D' cannot be used as type parameter `T' in the generic type or method `Articulos.Cap03.ClaseGenerica'. There is no implicit reference conversion from `Articulos.Cap03.D' to `Articulos.Cap03.A'

> Prueba de compilación.

2.1 Múltiples restricciones

Si el tipo genérico que estamos declarando posee dos o más argumentos o parámetros de tipo, para cada uno podemos establecer restricciones para cada uno. Así:

public class ClaseGenerica<T, U>
where T : ClaseConcreta
where U : Estructura
{}

2.2 Restricción new()

A través de la restricción new() como en:

public class TipoGenerico<T> where T : IComparable, new()
{
}

Con esto le estamos diciendo al compilador que cualquier argumento de tipo debe poseer un constructor sin argumentos (o constructor predeterminado) [8].

2.3 Parámetros de tipo ilimitado

Se dice que un parámetro de tipo ilimitado es aquel que no establece ninguna restricción. Verbigracia:

public class ClaseGenerica<T> { }

El hecho de no establecer la palabra clave contextual where lo establece de esa manera. Sin embargo, hay que tener en mente las siguientes consideraciones que aplica para esta clase de parámetros de tipo [10]:
  • Los operadores de comparación ==, != no se pueden usar debido a que se desconoce que los argumentos concretos los soporten.
  • Se puede convertir desde y hacia Object, o a cualquier tipo de interfaz.
  • Se puede realizar la comparación con null. Empero, hay que tener en cuenta que para tipos por valor, la comparación generará siempre false.

2.4 Parámetros de tipo como restricciones

En un método podemos definir una restricción sobre un parámetro de tipo contextual o local a ese mismo método. Ejemplifiquemos:

public class Lista<T>
{
void Agregar<U>(Lista<U> elementos) where U : T
{
// Implementación
}
}

El parámetro de tipo del método Agregar -U- está restringido a la naturaleza del parámetro de tipo T de Lista.

Este concepto, también se puede aplicar para la definición de una clase genérica con múltiples parámetros (argumentos) de tipo. Así:

public class ClaseGenerica<T, U, V> where T : V { }

En [10] explicitan su utilidad:
Use type parameters as constraints on generic classes in scenarios in which you want to enforce an inheritance relationship between two type parameters.

2.5 Resumen: seis tipos de restricciones de parámetros de tipo

En la Figura 2 [10] se muestra el listado de los seis tipos de restricciones sobre los parámetros (argumentos) de tipo:
Seis tipos de restricciones
Figura 2. Seis tipos de restricciones [10].

3. Pautas para la Nomenclatura de Parámetros de Tipo

3.1 Nombres descriptivos

Los nombres de los parámetros de tipo deben ser lo mayormente descriptivos posibles. Ejemplos [2]:

public class List<T> { }

public delegate TSalida Convertidor<TEntrada, TSalida>(TEntrada from);

public interface ISessionChannel<Tession> {}

3.2 Nombres cortos

Siempre que sea posible, utilicemos una sola letra para el parámetro de tipo. Ejemplos [2]:

public int IComparer<T> () { return 0; }

public delegate bool Predicado<T> (T elemento);

public struct Nullable<T> where T : struct { }

3.3 Prefijo T

Por convención histórica, los parámetros por tipos siempre llevan la letra T al inicio de su nombre. Ejemplos [2]:

public interface ISessionChannel<TSession>
{
TSession Session
{
get;
}
}

4. Conclusiones

Los parámetros de tipos se pueden ver como receptáculos de generalización de tipos de una jerarquía de herencia. Podemos cambiar la generalización a través de restricción para el parámetro (argumento) de tipo. Si un tipo genérico posee varios parámetros de tipo, es posible establecer una restricción para cada uno de ellos. También debemos considerar las pautas de nomenclatura para los parámetros de tipo para adaptarnos a las convenciones naturales.

5. Glosario

  • Contenedor
  • Herencia
  • Generalización
  • Parámetro de tipo
  • Plantilla
  • Receptáculo de tipo

6. 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]: Tipos Genéricos en C# - Parte 1: Introducción - http://ortizol.blogspot.com/2014/05/tipos-genericos-en-c-parte-2-beneficios.html
[3]: Tipos Genéricos en C# - Parte 2: Beneficios | OrtizOL - Experiencias Construcción Software (xCSw) - http://ortizol.blogspot.com/2014/05/tipos-genericos-en-c-parte-2-beneficios.html
[4]: Compiler Error CS0246 - http://msdn.microsoft.com/en-us/library/w7xf6dxs.aspx
[5]: Compiler Error CS1525 - http://msdn.microsoft.com/en-us/library/3hdyz4dw(v=vs.90).aspx
[6]: Compiler Error CS1031 - http://msdn.microsoft.com/en-us/library/02az0sy4(v=vs.90).aspx
[7]: where clause (C# Reference) - http://msdn.microsoft.com/en-us/library/vstudio/bb311043(v=vs.110).aspx
[8]: where (generic type constraint) (C# Reference) - http://msdn.microsoft.com/en-us/library/bb384067.aspx
[9]: Compiler Error CS0311 - http://msdn.microsoft.com/en-us/library/bb384252.aspx
[10]: Constraints on Type Parameters (C# Programming Guide) - http://msdn.microsoft.com/en-us/library/d5x73970.aspx


J

No hay comentarios:

Publicar un comentario

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