Covarianza y contravarianza en C#

 

Esto son otros nuevos conceptos que vienen con el Framewrok 4.0 de C# y son duros de entender. Mi propósito en este articulo, como el de muchos autores, es dar a entender los conceptos que hay detrás de la covarianza y contravarianza, para que así estos conceptos se puedan aplicar de una manera más natural, en los desarrollos de Software que así lo requieran.
 
Empecemos por indicar que, desde el punto de la física,
 
Co y Contra: Implican dualidad, lo que significa que los términos van juntos.
Varianza: implica movimiento.
 
Se puede inferir, entonces, que en estos términos, Covarianza significa “Con el movimiento” y Contravarianza “Contra el movimiento o en sentido opuesto al movimiento”.
 
Ahora, en nuestro campo, la programación orientada a objetos, en el marco del concepto de herencia, identificamos dos tipos de objetos:
 
1.       Tipos base
2.       Tipos derivados
 
Así las cosas, podemos imaginarnos a un tipo base interactuando en un programa (es decir, tener un movimiento o acción o cambio)  y sustituir el tipo base por el tipo derivado, ya que el tipo derivado “es un” tipo base. Por tanto, cuando es equivalente tener a un tipo derivado en lugar del tipo base, estamos ante la covarianza. La acción o movimiento que afecte al tipo base, le afecta en igual medida al tipo derivado y, por ello, son covariantes.
 
En sentido opuesto, se define la contravarianza, en donde es el tipo derivado el que se puede sustituir por el tipo base.
 
En la siguiente imagen, se trata de ilustrar ambos conceptos:

 CoContraVarianza
 
Con el auge y manipulación de delegados, se pensó en incorporar, en el lenguaje, características que permitieran realizar manipulaciones de estos, esperadas, dada una relación de herencia entre tipos dados.
 
Ejemplo:
 
Gato hereda de Animal y, entonces, se puede tener el siguiente delegado genérico:
 
delegate T Func1<T>();
 
Siendo así las cosas, podemos tener una instancia de este delegado que retorne un gato:
 
 
public Cat MyFunc()
{
    return new Cat();
   }
 
    Func1<Cat> cat = MyFunc;
 
 
Debido a que un Gato es un Animal, tenderíamos a pensar, que debería ser viable asignar un Gato a un delegado que retorna un Animal, es decir, tener algo así:
 
Func1<Animal> animal = cat;
 
Sin embargo, hasta antes del Framework 4.0, una instrucción como esta sacaría error. A partir del Framework 4.0, es posible realizar la asignación, haciendo un cambio mínimo en la declaración del delegado. En particular, se requiere usar la palabra clave out, así:
 
delegate T Func1<out T>();
 
De esta forma, se puede lograr la asignación que hasta antes del Framework 4.0 no era posible y que se conoce como covarianza.
 
Ejemplo:
 
class Animal { }   

class Cat: Animal { }  

class Program    {    

delegate T Func1<out T>();      

static void Main(string[] args)      {          

   // Covariance           

   Func1<Cat> cat = () => new Cat();           

   Func1<Animal> animal = cat;           

}

}
 
La contravarianza tiende a ser más complicada de ilustrar. Aquí, se trata de sustituir el tipo derivado por el tipo base. Una de las situaciones donde a partir del Framework 4, se permite esta sustitución es en el paso de parámetros. La contravarianza se permite es en el paso de parámetros a diferencia de la covarianza que se permite en el retorno de tipos.
 
Ejemplo:
 
class Animal { }   

class Cat: Animal { }   

class Program    {     

delegate void Action1<in T>(T a);      

static void Main(string[] args)      {           

    // Contravariance           

             Action1<Animal> act1 = (ani) => { Console.WriteLine

                             (ani); };           

              Action1<Cat> cat1 = act1;           

           }
 
}
 
De nuevo, debido a que un Gato es un Animal, es esperable y deseable que los delegados que reciben parámetros de tipo Animal se puedan asignar a delegados que reciben Gatos como parámetro.
 
También, es posible, en Enumerados, utilizar las palabras in y out, en su definición, para lograr implementaciones de covarianza y contravarianza respectivamente. De hecho, en el Framework 4.0, ya la definición de la clase Enumerable<T> se modifica por Enumerable<Out T> y, por tanto, podemos tener este código:
 
IEnumerable<Cat> cats = new List<Cat>();
IEnumerable<Animal> animals = cats;
 
Es decir, podemos tener una covarianza, a nivel de enumerados.
 
De nuevo, esto es lo esperable y deseable, ya que como un Gato es un Animal se espera que un enumerado de gatos sea un enumerado de animales.
 
Otra forma de entender los conceptos de porque se llamarón covarianza y contravarianza, es dibujar flechas y asumir que la dirección de la flecha es la dirección del movimiento. Así:
 
En herencia tenemos una conversión de referencia implícita de Animal a Gato:
 
Animal → Gato
 
Y, como vimos, anteriormente, se permite en los enumerados, tener también un conversión de referencia implícita de IEnumerable<Animal> a IEnumerable<Gato>:
 
IEnumerable<Animal> → IEnumerable<Gato>
 
Se llama covarianza porque las flechas apuntan en la misma dirección o siguen el mismo movimiento.
 
En la contravarianza, la flecha va en el sentido contrario:
 
Animal → Gato
IComparable<Animal> ← IComparable<Gato>
 
En términos generales, se puede concluir que la covarianza y la contravarianza se refieren al uso de las palabras reservadas out e in respectivamente, en interfaces genéricas y delegados, para establecer relaciones entre tipos que mantienen la misma conversión de referencia implícita, que los tipos originales tenían en una relación de herencia (covarianza) o donde dicha conversión va en sentido inverso (contravarianza).

Comentarios

Entradas populares de este blog

Visual Studio 2012 Backup and Restore bases de datos

Configuración de expresiones en Quartz

Hacer que Windows XP luzca como Windows 7