Los genéricos en C# son una característica que nos permite definir clases, interfaces y métodos dependientes de un tipo de dato (qué, logicamente, que puede variar).
Este tipo de dato se especifica en tiempo de compilación, proporcionando flexibilidad y seguridad de tipo sin sacrificar el rendimiento.
Los genéricos nos permiten escribir métodos y clases que funcionan con distintos tipo de dato, a la vez que mantienen la detección de errores de tipo en tiempo de compilación
Creación y uso de clases genéricas
Definición de una clase genérica
Para definir una clase genérica, se utiliza el símbolo <T> después del nombre de la clase. Aquí, T es un parámetro de tipo que puede ser reemplazado por cualquier tipo específico cuando se crea una instancia de la clase.
public class MiClase<T>
{
private T contenido;
public MiClase(T contenido)
{
this.contenido = contenido;
}
public T ObtenerContenido()
{
return contenido;
}
public void MostrarContenido()
{
Console.WriteLine($"El contenido de la caja es: {contenido}");
}
}
En realidad <T> es un convencionalismo, heredado del Templating (una característica de C++). Pero cualquier otro nombre es posible. Por ejemplo, así
public class MiClase<tipo1>
{
// ... contenido de MiClase
}
En muchas ocasiones es frecuente usar nombres descriptivos para los parámetros de tipo, como mejora de la legibilidad del código. En lugar de T, considera usar nombres como TElemento, TResultado, etc.
Instanciación de una clase genérica
Ahora, para instanciar una clase genérica, debemos especificar el tipo de dato que se utilizará en lugar del parámetro de tipo T (o como hayáis llamado al tipo).
// Para integer
MiClase<int> cajaDeEnteros = new MiClase<int>(123);
cajaDeEnteros.MostrarContenido(); // El contenido de la caja es: 123
// Para string
MiClase<string> cajaDeCadenas = new MiClase<string>("Hola");
cajaDeCadenas.MostrarContenido(); // El contenido de la caja es: Hola
Genéricos en colecciones
Las colecciones genéricas en .NET (en el espacio de nombres System.Collections.Generic) son muy utilizadas, y ofrecen una alternativa más segura y eficiente a las colecciones no genéricas.
Algunas de las colecciones genéricas más comunes incluyen:
List<T>Dictionary<TKey, TValue>Queue<T>Stack<T>
Por ejemplo, veamos como crear una List para números enteros y para cadenas de textos.
// lista enteros
List<int> numeros = new List<int> { 1, 2, 3, 4, 5 };
// lista de cadenas de texto
List<string> numeros = new List<string> { "A", "B", "C", "D", "E" };
Restricciones de tipos genéricos
Las restricciones permiten limitar los tipos que se pueden usar como argumentos para los parámetros de tipo. Esto se logra utilizando la palabra clave where.
public class Almacen<T> where T : class
{
private List<T> items = new List<T>();
public void Agregar(T item)
{
items.Add(item);
}
public T Obtener(int indice)
{
return items[indice];
}
}
En este ejemplo, la restricción where T : class asegura que solo se puedan usar tipos de referencia como argumentos de tipo para T.
Algunas restricciones comunes incluyen:
| Condición | Descripción |
|---|---|
T: struct | El tipo debe ser un tipo de valor |
T: class | El tipo debe ser una referencia |
T: new() | El tipo debe tener un constructor sin parámetros. |
T: <BaseClass> | El tipo debe ser o derivar de una clase base específica |
T: <Interface> | El tipo debe implementar una interfaz específica |
Métodos genéricos con múltiples tipos
Un método genérico con múltiples parámetros de tipo permite especificar más de un tipo en la definición del método.
public class Convertidor
{
public TResult Convertir<TInput, TResult>(TInput input)
{
TResult resultado = // lo que fuera con input
return resultado;
}
}
En este ejemplo, el método Convertir acepta un parámetro de tipo TInput y devuelve un valor de tipo TResult.