🎃 Grandes descuentos en libros en línea, eformaciones y vídeos*. Código CALABAZA30. Pulse aquí
¡Acceso ilimitado 24/7 a todos nuestros libros y vídeos! Descubra la Biblioteca Online ENI. Pulse aquí
  1. Libros
  2. C# 10
  3. Tipos genéricos
Extrait - C# 10 Desarrolle aplicaciones Windows con Visual Studio 2022
Extractos del libro
C# 10 Desarrolle aplicaciones Windows con Visual Studio 2022
4 opiniones
Volver a la página de compra del libro

Tipos genéricos

Introducción

Los tipos genéricos permiten combinar la reutilización del código y la seguridad del tipo. Los tipos genéricos se usan más habitualmente para las colecciones. El Framework .NET expone estas colecciones en el espacio de nombres System.Collections.Generic, como el tipo List<T> o Dictionary<TKey, TValue>. Como es lógico, es posible crear sus propios tipos genéricos para crear una solución adaptada.

La ventaja de los tipos genéricos respecto a las colecciones clásicas como el tipo ArrayList es que el tipo de los objetos se conserva, mientras que una colección no genérica almacena los datos, haciendo una conversión al tipo Object. La recuperación de un elemento de una colección clásica obliga a hacer la conversión inversa, para hacer corresponder con el tipo esperado, mientras que los tipos genéricos devuelven un objeto ya tipado. A pesar de que la complejidad de codificación es ligeramente superior, los tipos genéricos además aportan mucha más rapidez, sobre todo cuando los elementos de la lista son tipos por valor.

La creación de tipos genéricos

Un tipo genérico se define en la declaración de la clase, indicando el parámetro T. Este parámetro especifica que el tipo lo elegirá el consumidor de la clase. Puede ser un tipo por valor o por referencia.

Cree una nueva clase ReportChangeList<T> en la carpeta Library del proyecto:

public class ReportChangeList<T>  
{  
} 

El parámetro T acepta un tipo, que se especificará durante la instanciación:

ReportChangeList<int> Ex1 = new ReportChangeList<int>();  
ReportChangeList<Object> Ex2 = new ReportChangeList<Object>(); 

Un tipo genérico también puede hacer referencia a varias clases:

public class ClaseGenerica<T, U> 

Los tipos genéricos se pueden sobrecargar, siempre que el número de sus parámetros de tipo no sea idéntico:

public class ClaseGenerica<T>  
public class ClaseGenerica<T, U> 

Si dos tipos genéricos tienen el mismo nombre y número de parámetros de tipo, el compilador devolverá un error:

public class ClaseGenerica<T>  
public class ClaseGenerica<U>         // No autorizado 

Los atributos genéricos también pueden crearse siguiendo los mismos principios:

public class AtributoGenerico<T> : Atributo { } 

El atributo podrá englobar...

Las restricciones de tipo

Como hemos visto en los descriptores de acceso, la interfaz IReportChange se usa para acceder a la propiedad HasChanged de los elementos de la lista. Esto significa que los elementos añadidos deben implementar obligatoriamente la interfaz. Para limitar los tipos que pueden reemplazar al parámetro genérico T, se pueden aplicar restricciones.

A continuación, se muestran las restricciones existentes:

Restricción

Descripción

where T:clase

Restricción sobre la clase base del tipo.

where T:interfaz

Restricción sobre la interfaz que implementa el tipo.

where T: class

El tipo debe ser un tipo por referencia.

where T: struct

El tipo debe ser una estructura.

where T: new()

El tipo debe tener un constructor sin parámetro.

where U: T

El tipo representado por U debe ser idéntico al tipo T.

Añada una restricción a la interfaz IReportChange sobre la clase genérica ReportChangeList:

public class ReportChangeList<T>: IReportChildrenChange where T:  
IReportChange 

Las restricciones se aplican a todos los parámetros de tipo definidos, ya sea en los métodos o en la definición de tipo.

Las restricciones de tipo también se pueden definir a nivel de los métodos:

public void MiMetodo<T>() where T: class  
{ } 

Las interfaces genéricas

Como para las listas, sería interesante poder hacer un bucle foreach sobre los elementos de nuestro tipo genérico. Para esto es suficiente con implementar la interfaz genérica IEnumerable<T>:

public class ReportChangeList<T>: IReportChildrenChange,   
IEnumerable<T> where T: IReportChange 

Esta interfaz genérica se comporta como una interfaz clásica, contiene las declaraciones de los miembros necesarios para su implementación y se puede utilizar la clase genérica como referencia.

Añada los miembros GetEnumerator() e IEnumerable.GetEnumerator() siguientes, para implementar la interfaz en la clase genérica ReportChangeList

public IEnumerator<T> GetEnumerator()  
{  
    for (int i = 0; i < this.children.Count; i++)  
    {  
        yield return this.children[i];  
    }  
}  
IEnumerator IEnumerable.GetEnumerator()  
{  
    return GetEnumerator();  
} 

La implementación de un enumerador consiste en hacer un bucle sobre los elementos de la lista y devolver cada uno de ellos a la llamada. La palabra clave yield return permite hacer un retorno a la llamada con el valor a devolver en función del valor anterior devuelto. El estado del método se mantiene de tal manera que puede continuar su ejecución hasta la siguiente llamada. El tiempo de vida de este estado está relacionado con el enumerador, el estado del método se elimina cuando la enumeración termina.

1. La varianza en las interfaces genéricas

La covarianza y la contravarianza son conceptos utilizados desde la versión 2.0 del Framework .NET. La versión 4 del Framework .NET añade la posibilidad de aplicar esos principios a las interfaces genéricas....

La creación de métodos genéricos

La declaración de los miembros de una clase genérica se hace de la misma manera que los miembros de clases clásicos. Además es posible usar el parámetro T para especificar el tipo de parámetro autorizado.

Añada el método Add a la clase ReportChangeList:

public void Add(T Child)  
{  
    IReportChange child = (IReportChange)Child;  
    child.Changed += new EventHandler(ChildChanged);  
    this.children.Add(Child);  
} 

El método Add recibe como parámetro un tipo que cumple las restricciones de la clase. El objeto que se pasa como parámetro al método implementa, por tanto, la interfaz IReportChange y se puede convertir al tipo de la interfaz, lo que permite al objeto de tipo ReportChangeList suscribirse al evento Changed del objeto que se pasa como parámetro antes de añadirlo a la lista children.

La lista va a contener muchos elementos, por lo que hay que implementar una técnica más elaborada para diferenciarlos y asegurar su unicidad. Vamos a agregar un indexador base a una clave de tipo string. En primer lugar, esto implica crear una interfaz IKey en la carpeta Library:

public interfaz IKey  
{  
    string Key  
    {  
       ...

Valor por defecto genérico

Estudiando con detalle el descriptor de acceso get del indexador de la clase ReportChangeList, observe que si no hay ningún elemento de la lista que se corresponda con clave que se pasa como parámetro el valor de retorno es:

return default(T); 

Un tipo genérico puede asignar un tipo por valor o por referencia. Los tipos por valor no pueden tener el valor null, es imposible devolver null para el descriptor de acceso get. Se usa la palabra clave default para obtener el valor por defecto del parámetro de tipo. De esta manera, para un parámetro de tipo por referencia se devuelve el valor null y para un tipo por valor se devuelve su valor por defecto. Si T representa el tipo int, el valor por defecto devuelto será cero.

La herencia de clase genérica

Una clase genérica puede heredar de otra clase. Las declaraciones de las clases hijas pueden jugar con los parámetros de tipo de la clase padre:

  • La clase heredada puede guardar el mismo parámetro de tipo:

class Padre<T>  
{ }  
class Hija<T>: Padre<T>  
{ } 
  • La clase heredada también puede especificar el parámetro de tipo:

class Hija: Padre<int>  
{ } 
  • La clase heredada puede introducir nuevos parámetros de tipo:

class Hija<T, T2>: Padre<T>  
{ } 
  • El nombre del parámetro de tipo del padre se puede renombrar y usar en la declaración del tipo hijo:

class Hija<Tx, T2>: Padre<Tx>  
{ }