Clases genéricas en C#
Más de C#. En este post toca hablar sobre los genéricos
Los genéricos, y más concretamente en este post los tipos genéricos, son un mecanismo de C# que nos ayuda a maximizar la reutilización de código. La reutilización de código se logra a través de el uso de plantillas (templates) de código en las cuales se insertan marcadores (placeholders) que representarán los tipos de dato que usaremos en nuestra plantilla.
Sintaxis
La declaración de tipos genéricos se realiza casi como cualquier otro tipo, con la diferencia de que tenemos que declarar los placeholders entre signos <
y >
, por ejemplo,
public class Box<T>
En la cual le estamos indicando que usaremos un marcador llamado T
. Que se lee como "Box de T". Luego entonces dentro de la declaración de la clase podemos reutilizar el marcador T
tantas veces como queramos:
public T Content { get; private set; }
public Box(T content)
{
Content = content;
}
Para hacer uso de un tipo genérico la sintaxis es más o menos igual a cualquier tipo por referencia: debemos usar el operador new
, el constructor de la clase e indicarle dentro de <
y >
los tipos con los que queremos que se reemplaze el marcador.
var cajaDeEntero = new Box<int>(5);
var cajaDeString = new Box<string>("Hola mundo");
Console.WriteLine(cajaDeEntero.Content); // 5
Console.WriteLine(cajaDeString.Content); // Hola mundo
También podemos anidar declaraciones en los tipos genéricos:
var cajaDeCajas = new Box<Box<string>>(cajaDeString);
Console.WriteLine(cajaDeCajas); // [Box: Content=[Box: Content=Hola mundo]]
No hay "límite" en cuanto al nombre o cantidad de tipos que podemos usar, por ejemplo:
public class ComplexBox<T, Content1, Content2>
{
public Content1 C1 { get; set; }
public Content2 C2 { get; set; }
public T Item { get; set; }
public ComplexBox(Content1 c1, Content2 c2)
{
C1 = c1;
C2 = c2;
}
}
// ...
var cajota = new ComplexBox<double, decimal, float>(1, 2)
{
Item = 3
};
Restricción where
De entrada, si solo especificamos de esta manera Box<T>
los genéricos, podríamos crear objetos a partir de Box
sin limitacio
Es por eso que usando la cláusula where X : [Condición] podemos limitar qué tipo de datos aceptará nuestro tipo genérico como marcadores, por ejemplo, supongamos que tenemos las clases:
public class LimitedBox<T> where T : struct
// ...
public class LimitedBox<X0,X1>
where X0 : struct
where X1 : IEquatable<X1>
// ...
public class LimitedBox<T, U, V>
where T : struct
where U : IEquatable<U>
where V : new()
// ...
Estamos limitando a que:
LimitedBox<T>
únicamente acepte enT
, tipos que seanstruct
comodecimal
yDateTime
LimitedBox<X0, X1>
únicamente acepte enX0
, tipos que seanstruct
comodecimal
yDateTime
y enX1
objetos que implementen la interfazIEquatable
LimitedBox<T, U, V>
similar al anterior, solo que en el tercer parámetro,V
, únicamente aceptará objetos que tengan un constructor vacío que se a público
La lista completa de restricciones se encuentra en este enlace.
Ejemplos
Pero bueno, veamos un ejemplo.
Imaginate un programa para gestionar un deportivo, en el que existen equipos de futbol y de béisbol, compuestos por atletas, Athlete
, (que a su vez se divide en futbolistas, FootballPlayer
, y por beisbolistas, BaseballPlayer
):
public class Athlete
{
public string Name { get; set; }
public double Height { get; set; }
public double Weight { get; set; }
}
Ahora, para crear los equipos podríamos tener clases separadas para representar cada equipo:
public BaseballTeam(int maxMembers)
{
Members = new BaseballPlayer[maxMembers];
}
}
</div>
<div class="pure-u-1 pure-u-md-1-2">
```csharp
public class FootballTeam
{
public string CoachName { get; set; }
public FootballPlayer[] Members { get; set; }
public FootballTeam(int maxMembers)
{
Members = new FootballPlayer[maxMembers];
}
}
Y para crear equipos tendríamos que hacer algo así:
var vitesse = new FootballTeam(18);
vitesse.Members[0] = new FootballPlayer { Name = "Alex Renato Ibarra Mina" };
var losAngeles = new BaseballTeam(25);
losAngeles.Members[0] = new BaseballPlayer { Name = "Julio César Urías" };
Genéricos
O, para maximizar la reutilización de código, podríamos crear una clase genérica, digamos Team<T>
("Team de T"), para poder reutilizarla (y no solo con equipos de futbol o beisbol):
public class Team<T>
{
public string CoachName { get; set; }
public T[] Members { get; set; }
public Team(int maxMembers)
{
Members = new T[maxMembers];
}
}
Y usarlo de esta manera:
var vitesse = new Team<FootballPlayer>(18);
vitesse.Members[0] = new FootballPlayer { Name = "Alex Renato Ibarra Mina" };
var losAngeles = new Team<BaseballPlayer>(25);
losAngeles.Members[0] = new BaseballPlayer { Name = "Julio César Urías" };
Si dejamos la clase Team<T>
como está, podríamos hacer cosas como esta:
var equipoDeEnteros = new Team<int>(10);
var otroEquipo = new Team<object>(1233);
Que tal vez no tienen mucho sentido en nuestra aplicación. Es por eso que usando la restricción where
podemos limitar al tipo Team<T>
para que únicamente acepte Athlete
s:
public class Team<T> where T : Athlete
Ejemplos del Framework
En el Framework .NET existen numerosos ejemplos de tipos genéricos, siendo el más usado y conocido, las colecciones genéricas y las tuplas en C#.
Para cerrar
Usar los genéricos permiten dos cosas: Incrementar la reutilización de código manteniendo el tipado fuerte y reducir el impacto al desempeño en nuestras apps al evitar el boxing y unboxing, de ahí la importancia de su existencia.
Además, los genéricos existen también para métodos, post sobre el que estaré escribiendo en el futuro.