Desde la versión del Framework 2.0 llego el concepto de Generics, este nos da la posibilidad de definir clases genéricas, donde el cliente de la misma, es el encargado de definir que tipo desea utilizar.
Las ventajas más importantes son las siguientes
- Nos evitamos el cast innecesario de objetos y en algunos casos el Boxing y Unboxing
- Tenemos la posibilidad de hacer chequeo de tipos en tiempo de compilación
- Ganamos claridad en el código
Uno de los lugares donde es utilizado, es en las colecciones de objetos, de ahí la aparición de un nuevo espacio de nombre System.Collections.Generics donde encontramos un grupo de clases muy útiles, referidas al manejo de colecciones con tipos genéricos.
Antes de Generics, la manera mas fácil de tener una colección en memoria, que nos permita
- Recorrer sus elementos
- Buscar un elemento por índice
- Agregar y quitar elementos despues de haber definida la colección
es utilizar la clase ArrayList, que incluso nos deja definir una colección con elementos de tipos diferente, con el problema de no poder hacer ningún tipo de chequeo, además de los problemas de performance referidos a los Cast innecesarios.
Con la llegada de Generics, tenemos una nueva clase llamada List<T>, que viene a cumplir en gran medida con lo que la mayoría de los usuarios de ArrayList esperábamos.
La posibilidad de tener Colecciones de objetos realmente genéricas, donde al momento de utilizarlas uno pueda definir con que tipo quiere trabajar, aprovechando así el chequeo de tipos, Intellisense, sin Cast innecesarios, sin Boxing y Unboxing para tipos por valor, y hasta logrando un código mas legible.
Veamos algunos ejemplos utilizando clases cutom genéricas, para familiarizarnos con la sintaxis, y luego como utilizar estas nuevas listas.
Supongamos un ejemplo ilustrativo, donde queremos tener una propiedad Edad que pueda ser String o Int según la implementación.
Clases cutom sin utilizar Generics
La única manera de tener un tipo Genérico era definirlo como Object
public class Persona
{
private object edad;
public object Edad
{
get { return edad;}
set { edad = value;}
}
}
Caso 1, con un tipo por referencia
Persona per = new Persona();
per.Edad = "25";
// En este caso da un error de compilación
// int edad = (int)per.Edad;
// Aca funicona, pero tuvimos que hacer un CAST
string edad = (string)per.Edad;
Caso 2, con un tipo por valor
Persona per = new Persona();
// Boxing innecesario
per.Edad = 25;
// Aca funicona, pero tuvimos que hacer un CAST
int edad = (int)per.Edad;
// Esto falla por no ser un String
// string edad = (string)per.Edad;
Clases cutom con Generics
public class Persona<T>
{
private T edad;
public T Edad
{
get { return edad; }
set { edad = value; }
}
}
Caso 1, con un tipo por referencia
Persona<string> per = new Persona<string>();
per.Edad = "25";
// Esto funciona sin necesidad de CAST
string edad = per.Edad;
// Esto código no compila, evitando problemas posteriores
// int edad = per.Edad;
Caso 2, con un tipo por valor
Persona<int> per = new Persona<int>();
per.Edad = 25;
// Esto código no compila, evitando problemas posteriores
// string edad = per.Edad;
// Esto funciona sin necesidad de CAST
int edad = per.Edad;
Listas genéricas
Ahora apliquemos esto a las Listas genéricas, veamos un ejemplo muy común donde uno necesita definir una lista de un tipo de dato en particular, en este caso con un tipo entero.
int suma = 0;
ArrayList numeros = new ArrayList();
numeros.Add(1); // Boxing innecesario
numeros.Add(2); // Boxing innecesario
numeros.Add(3); // Boxing innecesario
numeros.Add(4); // Boxing innecesario
//numeros.Add("cadorna"); Esta linea es valida pero falla en tiempo de ejecucion
foreach (int num in numeros) // Unboxing innecesario
{
suma += num;
}
int valor = (int)numeros[0]; // CAST innecesario al recuperar un valor
En cambio utilizando la nueva clase List<T> este código se veria así
List<int> numeros = new List<int>(); // Se define el tipo a utilizar
numeros.Add(1);
numeros.Add(2);
numeros.Add(3);
numeros.Add(4);
numeros.Add("cadorna"); // Error en tiempo de compilación
foreach (int num in numeros) // Valida en tiempo de compilación
{
suma += num;
}
int valor = numeros[0]; // No necesita CAST
Este segundo caso como se ve es mucho más eficiente.
Algunas conclusiones finales
Como se ve a la hora de necesitar una colección fácil de manipular, de agregar y quitar ítems es mejor utilizar la clase List<T> y dejar de lado ArrayList que solo seria utilizable si queremos mezclar tipos de datos dentro de la misma colección, algo que no es muy recomendable y que seria lo mismo que definir una lista de objetos List<object>.