miércoles, 30 de octubre de 2013

Restricciones en Generics



Hasta ahora, en la construcción de nuestros Generics, hemos dejado plena libertad para el consumidor, tanto si somos nosotros mismos, o si es algún usuario de alguna de nuestras librerías, de poder asignar el tipo que mejor le viniera dentro de nuestros parámetros de tipo. Esto en ocasiones, puede ser contraproducente para su uso, y lo que es peor puede limitarnos en demasía a la hora de realizar un desarrollo. Las restricciones dentro de Generics nos pueden ayudar a solventar todos estos problemas.





Recuerda que aquí tienes el indice de todos los posts del Curso de LinQ.




Cuando definimos nuestras clases, métodos genéricos, podemos aplicar restricciones al argumento ‘T’ introducido.

Si vemos el ejemplo del primer post de Generics:

public static void CambiarValores<T>(ref T a, ref T b)
{
    T _a = a;
    T _b = b;

    a = _b;
    b = _a;
}

Nuestro parámetro de tipo ‘T’, no tiene ningún tipo de restricción por lo que a la hora de hacer la llamada la podríamos realizar introduciendo cualquier tipo del CLR. Si inspeccionamos cualquiera de los parámetros mediante el intellisense, veremos que los métodos que nos proporciona son los relacionados con el tipo object, tipo base del CLR.














Si ponemos un poco de coherencia al método, atendiendo al nombre del mismo, CambiarValores, una buena idea sería restringir su uso solo para tipos por valor, deshabilitando los tipos por referencia, ya que estos cambiarían la dirección de memoria a la que apuntan, haciendo un poco confuso el uso del mismo.

Para lograr esto, utilizaremos la palabra reservada where al finalizar la definición del método y antes de las llaves, de la siguiente forma:

public static void CambiarValores<T>(ref T a, ref T b) where T : struct

Con esto le estamos indicando al método que los tipos válidos para el parámetro de tipo, solo podrán ser tipos por valor (struct).

















Como podemos apreciar en el pantallazo, la primera llamada con el tipo int (struct) es completamente válida para el compilador, mientras la segunda de tipo string (class) es señalada como error, ya que no es una estructura (non-nullable).


Tipos de restricción

Las restricciones admitidas por el framework son las siguientes link:

  • Where T: struct .- El tipo de argumento debe de ser un tipo por valor (struct).
  • Where T: class .- El tipo de argumento debe de ser un tipo por referencia (class).
  • Where T: new() .- El tipo de argumento debe tener un constructor sin parámetros. Si este tipo se usa con más de una restricción debe de ir indicado en último lugar.
  • Where T: <base class name> .- El tipo de argumento debe de derivar de la clase especificada.
  • Where T: <interface name> .- El tipo de argumento debe implementar la interfaz especificada.
  • Where T: U .- El tipo de argumento T debe de derivar del argumento U.


Puede existir más de un tipo de restricción, como en el ejemplo siguiente:

public static void CambiarValores<T>(ref T a, ref T b) where T : class, new()


Observaciones de restricciones de clase e interfaz

Cuando coges experiencia en la construcción con generics, al desarrollar un método genérico, no sueles predecir de primeras el añadirle ningún tipo de restricción, y terminas dándote cuenta de que esta es necesaria cuando tienes la necesidad acceder a una serie de métodos o propiedades de un ‘tipobase’ como mínimo. Esto se ve claro cuando accedes a los miembros de alguno de nuestros parámetros del método genérico en desarrollo, y te encuentras solo con los 4 miembros de tipo object disponibles, que no te sirven absolutamente par nada. Esto nos da la pista de que tenemos que realizar la restricción al  ‘tipo base’ deseado, para que el intellisense nos muestre los miembros que necesitamos disponibles.

Lo podemos ver en el siguiente ejemplo, en el que partimos de la clase base Animal:

public class Animal
{
    public string Nombre { get; set; }
    public double Peso { get; set; }
}

Como podemos apreciar en la imagen, sus miembros son accesibles desde el método:













Pues hasta aquí las restricciones en Generics, un tema muy importante para realizar nuestros propios métodos avanzados en LinQ.