2011-01-12 13 views
40

¿Podría alguien proporcionarme ejemplos simples de C# de convariancia, contravarianza, invarianza y contrainvariación (si tal cosa existe).Ejemplos simples de co y contravariancia

Todas las muestras que he visto hasta ahora estaban simplemente fundiendo algún objeto en System.Object.

+0

Alguien dio emitan un objeto a 'System.Object' como un ejemplo de covarianza? Eso ni siquiera está bien. –

+0

Lo tomé como que significa pasar algún tipo de objeto (con algún tipo específico, más derivado) en lugar de 'System.Object', no necesariamente convertir 'object' en' System.Object', lo que sería inútil. –

+0

_Variance_ no se debe confundir con _casting_, no son lo mismo. Ver: [Diferencia entre covarianza y upcasting] (http://stackoverflow.com/a/6707697/949681). – Pressacco

Respuesta

84

Podría alguien darme ejemplos simples de C# de convarianza, contravariancia, invarianza y contrainvarianza (si tal cosa existe).

No tengo idea de qué significa "contra-invariante". El resto es fácil.

Aquí está un ejemplo de la covarianza:

void FeedTheAnimals(IEnumerable<Animal> animals) 
{ 
    foreach(Animal animal in animals) 
     animal.Feed(); 
} 
... 
List<Giraffe> giraffes = ...; 
FeedTheAnimals(giraffes); 

La interfaz IEnumerable<T> es covariante. El hecho de que Jirafa sea convertible en Animal implica que IEnumerable<Giraffe> es convertible a IEnumerable<Animal>. Desde List<Giraffe> implementa IEnumerable<Giraffe> este código tiene éxito en C# 4; hubiera fallado en C# 3 porque la covarianza en IEnumerable<T> no funcionó en C# 3.

Esto debería tener sentido. Una secuencia de jirafas se puede tratar como una secuencia de animales.

He aquí un ejemplo de contravarianza:

void DoSomethingToAFrog(Action<Frog> action, Frog frog) 
{ 
    action(frog); 
} 
... 
Action<Animal> feed = animal=>{animal.Feed();} 
DoSomethingToAFrog(feed, new Frog()); 

El delegado Action<T> es contravariante. El hecho de que Frog sea convertible en Animal implica que Action<Animal> es convertible a Action<Frog>. Observe cómo esta relación es dirección opuesta de la covariante; es por eso que es la variante "contra". Debido a la convertibilidad, este código tiene éxito; hubiera fallado en C# 3.

Esto debería tener sentido. La acción puede tomar cualquier Animal; necesitamos una acción que pueda tomar cualquier Rana, y una acción que pueda tomar cualquier Animal seguramente también puede tomar cualquier Rana.

Un ejemplo de invariancia:

void ReadAndWrite(IList<Mammal> mammals) 
{ 
    Mammal mammal = mammals[0]; 
    mammals[0] = new Tiger(); 
} 

¿Podemos pasar una IList<Giraffe> a esta cosa? No, porque alguien va a escribir un tigre en él, y un tigre no puede estar en una lista de jirafas. ¿Podemos pasar un IList<Animal> en esto? No, porque vamos a leer un mamífero y una lista de animales podría contener una rana. IList<T> es invariante. Solo se puede usar como lo que realmente es.

Para algunas consideraciones adicionales sobre el diseño de esta característica, vea mi serie de artículos sobre cómo lo diseñamos y construimos.

http://blogs.msdn.com/b/ericlippert/archive/tags/covariance+and+contravariance/

+2

Podría valer la pena señalar en su último ejemplo, por supuesto, que * podría * lanzar una 'Jirafa []' a un 'Mammal []' y pasar eso, lo que daría como resultado un error de tiempo de ejecución. –

+3

@Dan: Buen punto. C# admite la covarianza de matrices "rotas", donde se permiten algunas conversiones covariantes aunque puedan causar bloqueos en el tiempo de ejecución. –

+0

@Eric: Sí, definitivamente entiendo el razonamiento allí (creo): a menudo los desarrolladores quieren un "subconjunto" de la interfaz de un 'T []' (o 'IList '), para acceder a los elementos por índice sin intención de escribir a la colección. Esta es la razón por la que personalmente creo firmemente que debería haber algo así como una interfaz coherente "IArray " con un getter indexado en el BCL. Esto permitiría que 'List ' actuara como' IList 'cuando solo se usa para acceso indexado y no para sus características variables (es decir, en el mismo caso donde' Jirafa [] 'puede actuar como 'Mamífero' [] '). –

3

La invarianza (en este contexto) es la ausencia tanto de contra-varianza como contra-varianza. Entonces la contrainvarianza no tiene sentido. Cualquier parámetro de tipo que no esté etiquetado como in o out es invariante. Esto significa que este parámetro de tipo puede ser consumido y devuelto.

Un buen ejemplo de co-varianza es IEnumerable<out T> porque obviamente un IEnumerable<Derived> se puede sustituir por un IEnumerable<Base>. O Func<out T> que devuelve valores del tipo T.
Por ejemplo, un IEnumerable<Dog> puede convertirse a IEnumerable<Animal> porque cualquier Perro es un animal.

Para contra-varianza puede usar cualquier interfaz o delegado consumidor. IComparer<in T> o Action<in T> vienen a mi mente. Estos nunca devuelven una variable de tipo T, solo la reciben. Y donde excepto para recibir un Base puede pasar un Derived.

Al considerarlos como parámetros de tipo solo entrada o solo salida, es más fácil entender la IMO.

Y la palabra Invariantes generalmente no se usa junto con la varianza de tipo, sino con invariantes de clase o método, y representa una propiedad conservada. Vea this stackover thread donde se discuten las diferencias entre invariantes e invariantes.

2

Si considera el uso regular de los genéricos, normalmente usa una interfaz para manejar un objeto, pero el objeto es una instancia de una clase: no puede crear una instancia de la interfaz. Use una lista simple de cadenas como ejemplo.

IList<string> strlist = new List<string>(); 

estoy seguro de que está consciente de las ventajas de utilizar un IList<> lugar de hacerlo directamente usando un List<>. Permite la inversión del control, y puede decidir que no desea usar un List<>, pero desea un LinkedList<> en su lugar. El código anterior funciona bien porque el tipo genérico de la interfaz y la clase es el mismo: string.

Sin embargo, puede ser un poco más complicado si quiere hacer una lista de cadenas. Considere este ejemplo:

IList<IList<string>> strlists = new List<List<string>>(); 

Esto claramente no se compilará, porque los tipos de argumentos genéricos IList<string> y List<string> no son los mismos. Incluso si declaró la lista externa como una clase normal, como List<IList<string>>, no se compilaría, los argumentos de tipo no coinciden.

Así que aquí es donde la covarianza puede ayudar. La covarianza le permite usar un tipo derivado más como argumento de tipo en esta expresión. Si IList<> se hizo para ser covariante, sería simplemente compilar y arreglar el problema.Por desgracia, no es covariante IList<>, pero uno de los interfaces que extiende es:

IEnumerable<IList<string>> strlists = new List<List<string>>(); 

Este código se compila ahora, los argumentos de tipo son los mismos que anteriormente.

Cuestiones relacionadas