miércoles, 14 de mayo de 2008

Culturilla: Covarianza en genéricos

Este es el primer post de una serie (espero que larga) de posts de culturilla: cada cuando pueda iré publicando posts con información sobre cosas interesantes (a mi criterio, claro :P) sobre .NET y que no está de más saber... vamos, como su nombre indica, un poco de culturilla sobre .NET.
Y para empezar, un tema que tarde o temprano todo el mundo se termina planteando: son covariantes los genéricos de .NET? O, lo que es lo mismo: si los perros son animales, entonces las listas de perros son listas de animales?

Todos estamos acostumbrados a hacer algo como esto:

class Animal { }

class Perro : Animal { }

class Program

{

static void Main(string[] args)

{

Domestica(new Perro());

}

static void Domestica(Animal a)

{

}

}


Es decir, pasar un Perro a un método cuya firma especifica que espera un Animal. Esto además es lógico puesto que si la clase Perro deriva de la clase Animal, entonces todos los perros son animales.
Dicho esto, uno esperaria entonces que el siguiente código funcionase también:

class Animal { }

class Perro : Animal { }

class Program

{

static void Main(string[] args)

{

DomesticaVarios(new List<Perro>());

}

static void DomesticaVarios(List<Animal> a)

{

}

}


Pero si compilamos este código, veremos que no compila. Visual Studio se nos quejará con un mensaje parecido a: cannot convert from 'System.Collections.Generic.List' to 'System.Collections.Generic.List'

Así que, aunque todos los Perros sean Animales, resulta que una lista de Perros NO es una lista de Animales. Es por eso que se dice que los genéricos en .NET no son covariantes.

Una vez que hemos comprobado esto, la pregunta es si esto es lógico o no... Hombre, a simple vista podríamos decir, que es lógico: de la misma manera que los métodos que trabajan con Animales funcionan con Perros, los que trabajen con una lista de Animales, deberían hacerlo con una lista de Perros... Entonces por que no funciona?

Primero por como funcionan los genéricos: List<Animal> y List<Perro> son dos clases completamente distintas, que tienen la misma relación que List<Animal> y List<Florecilla> por decir algo.
Otra razón que podríamos argumentar es para mantener la coherencia de la lista, si pudiesemos pasar una
List<Perro> a un método que espera una List<Animal> nada impide hacer esto:

static void Domestica(List<Animal> a)

{

a.Add(new Gato());

}


(Suponed que la clase Gato deriva de Animal).

En este caso, tenemos un List<Perro>... donde uno de sus elementos no es un Perro!!! No se si nunca habeis juntado varios perros con un gato, pero os puedo decir que se arma una de buena!

Este segundo argumento podria ayudarnos a entender un poco la no covarianza de genéricos en .NET... y digo solo un poco, porque para liar las cosas... los arrays si que son covariantes. Así, que si todavía quereis ver lo que pasa cuando se junta un gato con varios perros, siempre podeis hacer lo siguiente:

static void Main(string[] args)

{

Perro[] p = new Perro[10];

Domestica(p);

}

static void Domestica(Animal[] a)

{

a[1] = new Gato();

}


Y ahora la pregunta es... que hay en el array p, después de llamar a Domestica()????
Pues la respuesta es que el programa casca que da gusto con una ArrayTypeMismatchException, así que bueno... seguimos sin poder juntar perros con gatos :)

Saludos!

No hay comentarios: