Tengo algunos problemas para entender cómo usaría la covarianza y la contravarianza en el mundo real.
Hasta ahora, los únicos ejemplos que he visto han sido el mismo ejemplo de matriz anterior.
object[] objectArray = new string[] { "string 1", "string 2" };
Sería bueno ver un ejemplo que me permitiera usarlo durante mi desarrollo si pudiera verlo en otro lugar.
c#
c#-4.0
covariance
Maquinilla de afeitar
fuente
fuente
Respuestas:
Digamos que tiene una clase Persona y una clase que se deriva de ella, Maestro. Tienes algunas operaciones que toman
IEnumerable<Person>
como argumento. En su clase de escuela tiene un método que devuelve unIEnumerable<Teacher>
. La covarianza le permite usar directamente ese resultado para los métodos que toman unIEnumerable<Person>
, sustituyendo un tipo más derivado por un tipo menos derivado (más genérico). La contravarianza, contra intuitivamente, le permite usar un tipo más genérico, donde se especifica un tipo más derivado.Consulte también Covarianza y contravarianza en genéricos en MSDN .
clases :
Uso :
fuente
Por completitud…
fuente
void feed(IGobbler<Donkey> dg)
. Si tomaste un IGobbler <Quadruped> como parámetro, no podrías pasar un dragón que solo come burros.Esto es lo que armé para ayudarme a entender la diferencia.
tldr
fuente
Contravariance
ejemplo) cuandoFruit
es el padreApple
?Las palabras clave in y out controlan las reglas de conversión del compilador para interfaces y delegados con parámetros genéricos:
fuente
Aquí hay un ejemplo simple usando una jerarquía de herencia.
Dada la jerarquía de clases simple:
Y en código:
Invarianza (es decir, parámetros de tipo genérico * no * decorados con
in
oout
palabras clave)Aparentemente, un método como este
... debería aceptar una colección heterogénea: (lo que hace)
Sin embargo, pasar una colección de un tipo más derivado falla.
¿Por qué? Debido a que el parámetro genérico
IList<LifeForm>
no es covariante,IList<T>
es invariante, por lo queIList<LifeForm>
solo acepta colecciones (que implementan IList) dondeT
debe estar el tipo parametrizadoLifeForm
.Si la implementación del método
PrintLifeForms
fue maliciosa (pero tiene la misma firma de método), la razón por la cual el compilador evita que paseList<Giraffe>
es obvia:Dado que
IList
permite agregar o eliminar elementos, cualquier subclase deLifeForm
podría así agregarse al parámetrolifeForms
, y violaría el tipo de cualquier colección de tipos derivados pasados al método. (Aquí, el método malicioso intentaría agregar unZebra
avar myGiraffes
). Afortunadamente, el compilador nos protege de este peligro.Covarianza (genérico con tipo parametrizado decorado con
out
)La covarianza se usa ampliamente con colecciones inmutables (es decir, cuando no se pueden agregar o eliminar elementos nuevos de una colección)
La solución al ejemplo anterior es garantizar que se use un tipo de colección genérico covariante, por ejemplo
IEnumerable
(definido comoIEnumerable<out T>
).IEnumerable
no tiene métodos para cambiar a la colección, y como resultado de laout
covarianza, cualquier colección con subtipo deLifeForm
ahora puede pasarse al método:PrintLifeForms
ahora se puede llamar conZebras
,Giraffes
y cualquieraIEnumerable<>
de las subclases deLifeForm
Contravarianza (Genérico con tipo parametrizado decorado con
in
)La contravarianza se usa con frecuencia cuando las funciones se pasan como parámetros.
Aquí hay un ejemplo de una función, que toma un
Action<Zebra>
como parámetro y lo invoca en una instancia conocida de una cebra:Como se esperaba, esto funciona bien:
Intuitivamente, esto fallará:
Sin embargo, esto tiene éxito
e incluso esto también tiene éxito:
¿Por qué? Porque
Action
se define comoAction<in T>
, es decircontravariant
, significa que paraAction<Zebra> myAction
, esomyAction
puede ser "a lo sumo" aAction<Zebra>
, peroZebra
también son aceptables las superclases de menos derivadas .Aunque esto puede no ser intuitivo al principio (p. Ej., ¿Cómo se
Action<object>
puede pasar como un parámetro que requiereAction<Zebra>
?), Si desempaqueta los pasos, notará que la función llamada (PerformZebraAction
) en sí misma es responsable de pasar los datos (en este caso, unaZebra
instancia ) a la función: los datos no provienen del código de llamada.Debido al enfoque invertido de usar funciones de orden superior de esta manera, para cuando
Action
se invoca, es laZebra
instancia más derivada la que se invoca contra lazebraAction
función (pasada como parámetro), aunque la función en sí misma usa un tipo menos derivado.fuente
in
usa la palabra clave para la contravarianza ?Action<in T>
yFunc<in T, out TResult>
son contravariantes en el tipo de entrada. (Mis ejemplos utilizan los tipos existentes invariante (Lista), covariante (IEnumerable) y contravariante (Acción, Func))C#
, no lo sabría.Básicamente, cuando tenía una función que toma un Enumerable de un tipo, no podía pasar un Enumerable de un tipo derivado sin emitirlo explícitamente.
Sin embargo, solo para advertirte sobre una trampa:
Ese es un código horrible de todos modos, pero existe y el comportamiento cambiante en C # 4 podría introducir errores sutiles y difíciles de encontrar si usa una construcción como esta.
fuente
De MSDN
fuente
Contravarianza
En el mundo real, siempre puedes usar un refugio para animales en lugar de un refugio para conejos porque cada vez que un refugio de animales alberga un conejo es un animal. Sin embargo, si usa un refugio para conejos en lugar de un refugio para animales, su personal puede ser comido por un tigre.
En el código, esto significa que si usted tiene un
IShelter<Animal> animals
simplemente hay que escribirIShelter<Rabbit> rabbits = animals
si usted promete y su usoT
en elIShelter<T>
sólo como parámetros del método de esta manera:y reemplazar un ítem por uno más genérico, es decir, reducir la varianza o introducir contra varianza.
Covarianza
En el mundo real, siempre puedes usar un proveedor de conejos en lugar de un proveedor de animales porque cada vez que un proveedor de conejos te da un conejo, es un animal. Sin embargo, si usa un proveedor de animales en lugar de un proveedor de conejos, puede ser comido por un tigre.
En el código, esto significa que si tiene un
ISupply<Rabbit> rabbits
archivo, simplemente puede escribirISupply<Animal> animals = rabbits
si promete y usarT
elISupply<T>
único método como devolver valores como este:y reemplazar un elemento con un más derivada uno, es decir, aumentar la varianza o introducir co varianza.
Con todo, esto es solo una promesa comprobable en tiempo de compilación de usted de que trataría un tipo genérico de cierta manera para mantener la seguridad del tipo y no hacer que coman a nadie.
Es posible que desee leer esto para darle doble vuelta a esto.
fuente
contravariance
es interesante. Lo estoy leyendo como indicando un requisito operativo : que el tipo más general debe admitir los casos de uso de todos los tipos derivados de él. Entonces, en este caso, el refugio de animales debe ser capaz de soportar la protección de todo tipo de animales. ¡En ese caso, agregar una nueva subclase podría romper la superclase! Es decir, si agregamos un subtipo Tyrannosaurus Rex , podría destruir nuestro refugio de animales existente .El delegado convertidor me ayuda a visualizar ambos conceptos trabajando juntos:
TOutput
representa la covarianza donde un método devuelve un tipo más específico .TInput
representa la contravarianza donde se pasa un método de un tipo menos específico .fuente