[.NET] Qué son los Generics y su implementación en C# (y II).


En el anterior post vimos el concepto y las ventajas que tienen los Generics. Hoy veremos cómo emplear generics en clases, métodos, interfaces y delegados, y también conocer las características que tienen.

Qué es un parámetro de tipo genérico

Es un contenedor para un tipo de dato específico usado al crear una instancia de una variable generic. Por convención, los parámetros de tipo genérico vienen prefijados con la letra T y deben ser únicos en la declaración para evitar conflictos de nombres en la implementación.

Por ejemplo tenemos la siguiente declaración

public class List<T> {
   private T[] elements;
}

Como podemos observar, se puede usar el parámetro de tipo genérico (en nuestro caso T) en cualquier parte en la que se espera un tipo específico.

Así, si definimos una lista de enteros de la siguiente forma

List<int> miLista = new List<int>;

Dentro de la implementación, nuestro parámetro de tipo genérico se sustituye por el tipo específico int.

private int[] elements;

Generics

Clases genéricas

Cuando queremos que una clase implemente varias operaciones iguales sin tener en cuenta sobre qué tipo de dato actuar, empleamos los generics. Un ejemplo de esto son las clases de colecciones. Nos da igual que sea una colección de enteros, de cadenas o decimales, las operaciones básicas de la colecciones son las mismas y se realizan igual para cualquier tipo de dato.

Un ejemplo de definición de clase genérica es el siguiente:

public class MiClase<T>
{
   T value;

   public MiClase(T t)
   {
      this.value = t;
   }

   public void Write()
   {
     Console.WriteLine(this.value);
   }
}

Como se puede observar, esta clase tiene un método Write() que nos da igual el tipo de dato sobre el que actuar, escribirá la representación en texto del parámetro enviado al constructor.

Como características a conocer sobre generics tenemos:

  • Default values: 
    No es posible asignar null a tipos genéricos. La razón es que un tipo genérico también puede ser instanciado como un tipo por valor, y null está permitido solamente con tipos por referencia. Para evitar este problema, puede utilizar la palabra clave default. Con la palabra clave default, se asigna null a los reference types y un 0 a los value types.

    public class MiClase<T> {
        private T value;
    
        public T MiMetodo()
        {
            value = default(T);
            return value;
        }
    }
    
  • Constraints:
    Se pueden usar restricciones a la hora de definir el parámetro de tipo genérico para cuando se instancie la clase genérica. Si a la hora de instanciar una clase y el parámetro de tipo genérico no cumple la restricción establecida, tendremos un error en tiempo de compilación. Las restricciones se especifican mediante la palabra clave where.
    En la tabla siguiente se muestran los seis tipos de restricción.

    where T: struct El parámetro de tipo debe ser un tipo de valor.
    where T : class El parámetro de tipo debe ser un tipo de referencia.
    where T : new() El parámetro de tipo debe tener un constructor público sin parámetros.
    where T : base_class_name El parámetro de tipo debe ser la clase base especificada, o bien debe derivarse de la misma.
    where T : interface_name El parámetro de tipo debe ser o implementar la interfaz especificada.
    where T : U El parámetro de tipo proporcionado para T debe ser o derivar del parámetro proporcionado para U.

    Se pueden combinar varias restricciones, en el siguiente código tenemos que el parámetro de tipo genérico debe implementar el interfaz IEjemplo y debe tener un constructor sin parámetros.

    public class MiClase<T> where T: IEjemplo, new() {}
    
  • Inheritance: 
    Una clase puede implementar un interfaz

    public class MiClase<T>: IEnumerable<T> {}
    

    Un clase genérica puede derivar de una clase base genérica a su vez.

    public class Base<T> {}
    public class MiClase<T>: Base<T> {}
    

    El tipo de la clase base también puede especificarse

    public class Base<T> {}
    public class MiClase<T>: Base<string> {}
    

    Se permite definir una clase abstract como clase genérica base que es implementada con un tipo concreto en la clase derivada.

    public abstract class Calc<T>;
    {
        public abstract T Add(T x, T y);
        public abstract T Sub(T x, T y);
    }
    public class MiClase: Calc<int>
    {
        public override int Add(int x, int y)
        {
            return x + y;
        }
        public override int Sub(int x, int y)
        {
            return x — y;
        }
    }
    
  • Static members:
    Lo miembros estáticos de clases genéricas requieren especial atención. Sólo se comparten miembros estáticos de una clase genérica con una instanciación de la clase. Un ejemplo de esto es el siguiente código:

    public class MiClase<T>
    {
        public static int x;
    }
    
    MiClase<string>.x = 4;
    MiClase<int>.x = 5;
    Console.WriteLine(MiClase<string>.x); // escribe 4
    

Interfaces genéricos

Se pueden definir interfaces que tienen métodos con parámetros de tipo genérico. Cuando una interfaz se especifica como restricción en un parámetro de tipo, sólo se pueden utilizar los tipos que implementan la interfaz. Las clases que implementan un interfaz genérico pueden definirse de las siguientes formas.

public interface IEjemplo<T>
{
   public T MiMetodo();
}
public class MiClase: IEjemplo<int> {}
public class MiClase2<T>: IEjemplo<T> {}

Una clase puede implementar varios interfaces genéricos

public interface IEjemplo<T> {}
public interface IEjemplo2<T> {}
public class MiClase<T> where T : IEjemplo<T>, IEjemplo2<T> {}

Se puede aplicar la herencia entre interfaces genéricos

public interface IEjemplo<T> {}
public interface IEjemplo2 : IEjemplo<int> {}
public interface IEjemplo3<T> : IEjemplo<int> {}
public interface IEjemplo4<T> : IEjemplo<T> {}

Métodos genéricos

Un método genérico es aquel que en su declaración emplea parámetros de tipo genérico.

public static void MiMetodoGenerico<T>(T t)
{
   Console.WriteLine(t);
}

// para probarlo hacemos
int a = 0;
string b = "Esto es una prueba";

MiMetodoGenerico<int>(a);
MiMetodoGenerico<string>(b);

Está permitido crear métodos genéricos en clases no genéricas.

public class MiClase
{
   public static void MiMetodoGenerico<T>(T t)
   {
      Console.WriteLine(t);
   }
}

// para probarlo hacemos
int a = 0;
string b = "Esto es una prueba";
MiClase miClase = new MiClase();

miClase.MiMetodoGenerico<int>(a);
miClase.MiMetodoGenerico<string>(b);

Se pueden usar restricciones en los métodos para que sean más especializados

public interface IEjemplo<T> {}
public static void MiMetodoGenerico<T>(T t) where T : IEjemplo<T>
{
   Console.WriteLine(t);
}

Delegados genéricos

Un delegado puede definir sus propios parámetros de tipo genérico pero el código que hace referencia al delegado genérico debe especificar el tipo. En el siguiente post veremos qué es un delegado y cómo se usa.

Supongamos que tenemos un delegado definido de la siguiente manera

public delegate string GenericDelegate<T>(T t);

Ahora definimos dos métodos para que lo maneje nuestro delegado genérico.

public static string DecimalToString(decimal d)
{
  return d.ToString();
}  

public static string IntegerToString(int i)
{
  return i.ToString();
}

Como veremos en el siguiente ejemplo, instanciamos dos delegados especificando los tipos.

GenericDelegate<decimal> genericDelegateForDecimal = new GenericDelegate<decimal>(DecimalToString);
Console.WriteLine(genericDelegateForDecimal(99.78));  

GenericDelegate<int> genericDelegateForInt = new GenericDelegate<int>(IntegerToString);
Console.WriteLine(genericDelegateForInt(6));

Con esto terminamos la serie de dos artículos sobre Generics. Es una de las características de la plataforma .NET más potentes y nos facilita la reutilización de código. Para la realización de estos artículos he tomado como referencia el siguiente enlace de MSDN:

Generics (C# Programming Guide).

Espero os haya parecido interesante.

Anuncios

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s