Las clases genéricas encapsulan operaciones que no son específicas de un tipo de datos concreto. El uso más común de las clases genéricas se da con las colecciones, como listas vinculadas, tablas hash, pilas, colas, árboles, etc., en las que las operaciones tales como agregar y quitar elementos de la colección se realizan de forma similar independientemente del tipo de datos que se almacena. En la mayoría de los escenarios que necesitan clases de colección, el enfoque recomendado es utilizar las que proporciona la biblioteca de clases de .NET Framework 2.0. Para obtener más información acerca del uso de estas clases, vea Tipos genéricos en la biblioteca de clases de .NET Framework (Guía de programación de C#).
Normalmente, para crear clases genéricas se empieza a partir de una clase concreta existente y se cambian los tipos, uno a uno, por parámetros de tipo hasta que se obtiene un equilibrio óptimo entre generalización y utilidad. Al crear sus propias clases genéricas, se deben tener en cuenta las siguientes consideraciones importantes:
· Tipos que se generalizan como parámetros de tipo.
Como regla general, cuantos más tipos se puedan parametrizar, más flexible y reutilizable será el código. Sin embargo, un exceso de generalización puede producir código difícil de leer y comprender para otros programadores.
· Restricciones, en su caso, que se aplicarán a los parámetros de tipo (Vea Restricciones de tipos de parámetros (Guía de programación de C#)).
Una buena regla consiste en aplicar las restricciones máximas posibles que sigan permitiendo controlar los tipos que es necesario controlar. Por ejemplo, si sabe que la clase genérica está exclusivamente destinada al uso con tipos de referencia, aplique una restricción de clase. Esto evitará el uso imprevisto de la clase con tipos de valor y permitirá utilizar el operador as en T y comprobar si hay valores nulos.
· Factorizar o no el comportamiento genérico en las clases base y las subclases.
Dado que las clases genéricas pueden actuar como clases base, se aplican las mismas consideraciones de diseño que con las clases no genéricas. Vea más adelante las reglas para la herencia de clases base genéricas.
· Implementar o no una o varias interfaces genéricas.
Por ejemplo, si está diseñando una clase que se utilizará para crear elementos en una colección basada en genéricos, puede que necesite implementar una interfaz como I Comparable?<T>, donde T es el tipo de la clase.
Para obtener un ejemplo de una clase genérica simple, vea Introducción a los genéricos (Guía de programación de C#)
Las reglas para los parámetros de tipo y las restricciones tienen varias implicaciones en el comportamiento de la clase genérica, especialmente en lo relativo a la herencia y accesibilidad de los miembros. Antes de continuar, es importante entender algunos términos. Para una clase genérica Node<T>, el código de cliente puede hacer referencia a la clase especificando un argumento de tipo, para crear un tipo construido cerrado (Node<int>), o puede omitir la especificación del parámetro de tipo, por ejemplo al especificar una clase base genérica, para crear un tipo construido abierto (Node<T>). Las clases genéricas pueden heredar de clases base construidas cerradas o construidas abiertas:
C#
Copiar código
class Base Node? { } class Base Node Generic?<T> { } // concrete type class Node Concrete?<T> : Base Node { } //closed constructed type class Node Closed?<T> : Base Node Generic<int> { } //open constructed type class Node Open?<T> : Base Node Generic<T> { }
Las clases no genéricas (concretas) pueden heredar de las clases base construidas cerradas, pero no de las clases construidas abiertas ni de los parámetros de tipo ‘naked’, porque no hay ninguna forma de que el código de cliente pueda proporcionar el tipo de argumento necesario para crear una instancia de la clase base en tiempo de ejecución.
C#
Copiar código
//No error class Node1 : Base Node Generic<int> { } //Generates an error //class Node2 : Base Node Generic<T> {} //Generates an error //class Node3 : T {} Las clases genéricas que heredan de tipos construidos abiertos deben proporcionar argumentos de tipo para cada parámetro de tipo de clase base no compartidos con la clase que hereda, tal como se muestra en el código siguiente:
C#
Copiar código
class Base Node Multiple?<T, U> { } //No error class Node4<T> : Base Node Multiple<T, int> { } //No error class Node5<T, U> : Base Node Multiple<T, U> { } //Generates an error //class Node6<T> : Base Node Multiple<T, U> {} Las clases genéricas que heredan de tipos construidos abiertos deben especificar restricciones que impliquen o sean un superconjunto de las restricciones sobre el tipo base:
C#
Copiar código
class Node Item?<T> where T : System.I Comparable?<T>, new() { } class Special Node Item?<T> : Node Item<T> where T : System.I Comparable<T>, new() { } Los tipos genéricos pueden utilizar varios parámetros y restricciones de tipo, de la forma siguiente:
C#
Copiar código
class Super Key Type?<K, V, U> where U : System.I Comparable<U> where V : new() { } Los tipos construidos abiertos y construidos cerrados se pueden utilizar como parámetros de método:
C#
Copiar código
void Swap<T>(List<T> list1, List<T> list2) { //code to swap items } void Swap(List<int> list1, List<int> list2) { //code to swap items }
Las clases genéricas son invariables. En otras palabras, si un parámetro de entrada especifica una List<Base Class?>, obtendrá un error de compilación si intenta proporcionar una List<Derived Class?>.