c# - Ejemplos simples de co y contravariancia
.net covariance (3)
¿Podría alguien proporcionarme ejemplos simples de C # de convarianza, contravarianza, invarianza y contrainvariación (si tal cosa existe)?
No tengo idea de lo que significa "contrainvarianza". El resto es fácil.
Aquí hay un ejemplo de covarianza:
void FeedTheAnimals(IEnumerable<Animal> animals)
{
foreach(Animal animal in animals)
animal.Feed();
}
...
List<Giraffe> giraffes = ...;
FeedTheAnimals(giraffes);
La IEnumerable<T>
es covariante . El hecho de que Jirafa sea convertible en Animal implica que IEnumerable<Giraffe>
es convertible a IEnumerable<Animal>
. Dado que 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.
Aquí hay 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 de 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 la dirección opuesta a 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 invarianza:
void ReadAndWrite(IList<Mammal> mammals)
{
Mammal mammal = mammals[0];
mammals[0] = new Tiger();
}
¿Podemos pasar un IList<Giraffe>
a esto? 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>
a 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 ideas 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/
¿Podría alguien proporcionarme ejemplos simples de C # de convarianza, contravarianza, invarianza y contrainvariación (si tal cosa existe)?
Todas las muestras que he visto hasta ahora solo estaban lanzando algún objeto en System.Object
.
La invarianza (en este contexto) es la ausencia tanto de contra-varianza como contra-varianza. Entonces la palabra contrainvariación 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 un IEnumerable<Derived>
se puede sustituir por un IEnumerable<Base>
. O Func<out T>
que devuelve valores de tipo T
Por ejemplo, un IEnumerable<Dog>
se puede convertir 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>
viene a mi mente. Estos nunca devuelven una variable de tipo T
, solo la reciben. Donde sea que espere recibir una Base
, puede pasar un Derived
.
Pensar en ellos como parámetros de tipo solo de entrada o solo salida hace que sea más fácil de entender la OMI.
Y la palabra invariantes generalmente no se usa junto con la varianza de tipo, sino en el contexto de invariantes de clase o método, y representa una propiedad conservada. Vea este hilo de donde se discuten las diferencias entre invariantes e invariantes.
Si considera el uso regular de los genéricos, normalmente utiliza 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 conoce las ventajas de usar un IList<>
lugar de usar directamente un List<>
. Permite la inversión del control, y puede decidir que no quiere usar una List<>
más tiempo, pero desea una LinkedList<>
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. Considera este ejemplo:
IList<IList<string>> strlists = new List<List<string>>();
Esto claramente no compilará, porque los argumentos de tipos genéricos IList<string>
y List<string>
no son lo mismo. Incluso si declaraste 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 más derivado como argumento de tipo en esta expresión. Si se hiciera IList<>
para ser covariante, sería simplemente compilar y arreglar el problema. Desafortunadamente, IList<>
no es covariante, pero una de las interfaces que extiende es:
IEnumerable<IList<string>> strlists = new List<List<string>>();
Este código ahora compila, los argumentos de tipo son los mismos que estaban arriba.