Contradicción explicada

En primer lugar, he leído muchas explicaciones sobre SO y blogs sobre covarianza y contravarianza y un gran agradecimiento a Eric Lippert por producir una serie tan grandiosa sobre Covarianza y Contravarianza .

Sin embargo, tengo una pregunta más específica que estoy tratando de entender un poco.

Por lo que yo sé, según la explicación de Eric, Covariancia y Contravarianza son adjetivos que describen una transformación. La transformación covariante es la que conserva el orden de los tipos y la transformación contravariante es la que lo invierte.

Entiendo la covarianza de tal manera que creo que la mayoría de los desarrolladores entienden de manera intuitiva.

//covariant operation Animal someAnimal = new Giraffe(); //assume returns Mammal, also covariant operation someAnimal = Mammal.GetSomeMammal(); 

La operación de retorno aquí es covariante ya que estamos conservando el tamaño en el que tanto Animal es aún más grande que Mamífero o Jirafa. En ese sentido, la mayoría de las operaciones de retorno son covariantes, las operaciones contravariantes no tendrían sentido.

  //if return operations were contravariant //the following would be illegal //as Mammal would need to be stored in something //equal to or less derived than Mammal //which would mean that Animal is now less than or equal than Mammal //therefore reversing the relationship Animal someAnimal = Mammal.GetSomeMammal(); 

Esta pieza de código, por supuesto, no tendría sentido para la mayoría de los desarrolladores.

Mi confusión radica en los parámetros del argumento Contravariante. Si tuvieras un método como

 bool Compare(Mammal mammal1, Mammal mammal2); 

Siempre aprendí que los parámetros de entrada siempre fuerzan el comportamiento contravariante. Tal que si el tipo se usa como un parámetro de entrada, su comportamiento debe ser contravariante.

Sin embargo, cuál es la diferencia entre el siguiente código

 Mammal mammal1 = new Giraffe(); //covariant Mammal mammal2 = new Dolphin(); //covariant Compare(mammal1, mammal2); //covariant or contravariant? //or Compare(new Giraffe(), new Dolphin()); //covariant or contravariant? 

Por la misma razón que no puedes hacer algo como esto no puedes hacer

  //not valid Mammal mammal1 = new Animal(); //not valid Compare(new Animal(), new Dolphin()); 

Supongo que lo que estoy preguntando es qué hace que el argumento del método pase una transformación contravariante.

Perdón por la publicación larga, tal vez entiendo esto incorrectamente.

EDITAR:

Por alguna conversación a continuación, entiendo que, por ejemplo, el uso de una capa de delegado puede mostrar claramente la contradicción. Considera el siguiente ejemplo

 //legal, covariance Mammal someMammal = new Mammal(); Animal someAnimal = someMammal; // legal in C# 4.0, covariance (because defined in Interface) IEnumerable mammalList = Enumerable.Empty(); IEnumerable animalList = mammalList; //because of this, one would assume //that the following line is legal as well void ProcessMammal(Mammal someMammal); Action processMethod = ProcessMammal; Action someAction = processMethod; 

Por supuesto, esto es ilegal porque alguien puede pasar cualquier Animal a alguna Acción, mientras que el ProcessMammal espera algo que sea Mamífero o más específico (menor que Mamífero). Por eso, someAction tiene que ser solo Acción o algo más específico (Acción)

Sin embargo, esto está introduciendo una capa de delegates en el medio, ¿es necesario que para que ocurra una proyección contravariante tiene que haber un delegado en el medio? Y si tuviéramos que definir el Proceso como una interfaz, declararíamos el parámetro del argumento como un tipo contravariante solo porque no desearíamos que alguien pudiera hacer lo que había mostrado anteriormente con los delegates.

 public interface IProcess { void Process(T val); } 

Actualización: Ooops. Resultó que mezclé varianza y “compatibilidad de asignación” en mi respuesta inicial. Editó la respuesta en consecuencia. También escribí una publicación de blog que espero que responda mejor a estas preguntas: Preguntas frecuentes sobre la covarianza y la contradicción

Respuesta: Supongo que la respuesta a su primera pregunta es que no tiene contravariancia en este ejemplo:

 bool Compare(Mammal mammal1, Mammal mammal2); Mammal mammal1 = new Giraffe(); //covariant - no Mammal mammal2 = new Dolphin(); //covariant - no Compare(mammal1, mammal2); //covariant or contravariant? - neither //or Compare(new Giraffe(), new Dolphin()); //covariant or contravariant? - neither 

Además, ni siquiera tiene covarianza aquí. Lo que tiene se llama “compatibilidad de asignación”, lo que significa que siempre puede asignar una instancia de un tipo más derivado a una instancia de un tipo menos derivado.

En C #, la varianza es compatible con matrices, delegates e interfaces genéricas. Como dijo Eric Lippert en su publicación de blog ¿Cuál es la diferencia entre la covarianza y la compatibilidad de tareas? es que es mejor pensar en la varianza como “proyección” de tipos.

La covarianza es más fácil de entender, porque sigue las reglas de compatibilidad de asignación (la matriz de un tipo más derivado se puede asignar a una matriz de un tipo menos derivado, “objeto [] objs = nueva cadena [10];”). La contradicción revierte estas reglas. Por ejemplo, imagine que podría hacer algo como “string [] strings = new object [10];”. Por supuesto, no puedes hacer esto por razones obvias. Pero eso sería una contravariancia (pero, una vez más, las matrices no son contravariantes, solo admiten la covarianza).

Estos son los ejemplos de MSDN que espero que le muestren lo que realmente significa la contravarianza (ahora poseo estos documentos, por lo que si cree que algo no está claro en los documentos, no dude en darme su opinión):

  1. Uso de la varianza en interfaces para colecciones genéricas

     Employee[] employees = new Employee[3]; // You can pass PersonComparer, // which implements IEqualityComparer, // although the method expects IEqualityComparer. IEnumerable noduplicates = employees.Distinct(new PersonComparer()); 
  2. Usando la varianza en los delegates

     // Event hander that accepts a parameter of the EventArgs type. private void MultiHandler(object sender, System.EventArgs e) { label1.Text = System.DateTime.Now.ToString(); } public Form1() { InitializeComponent(); // You can use a method that has an EventArgs parameter, // although the event expects the KeyEventArgs parameter. this.button1.KeyDown += this.MultiHandler; // You can use the same method // for an event that expects the MouseEventArgs parameter. this.button1.MouseClick += this.MultiHandler; } 
  3. Uso de varianza para Func y delegates generics de acción

      static void AddToContacts(Person person) { // This method adds a Person object // to a contact list. } // The Action delegate expects // a method that has an Employee parameter, // but you can assign it a method that has a Person parameter // because Employee derives from Person. Action addEmployeeToContacts = AddToContacts; 

Espero que esto ayude.

La covarianza y la contradicción no son cosas que se pueden observar cuando se instalan clases. Por lo tanto, es incorrecto hablar de uno de ellos al mirar una instanciación de clase simple, como en su ejemplo: Animal someAnimal = new Giraffe(); //covariant operation Animal someAnimal = new Giraffe(); //covariant operation

Estos términos no clasifican las operaciones. Los términos Covarianza, Contravarianza e Invarianza describen la relación entre ciertos aspectos de las clases y sus subclases.

Covarianza
significa que un aspecto cambia de forma similar a la dirección de la herencia.
Contravariancia
significa que un aspecto cambia frente a la dirección de la herencia.
Invariancia
significa que un aspecto no cambia de una clase a su (s) subclase (s).

Generalmente consideramos los siguientes aspectos cuando hablamos de Cov., Contrav. y Inv .:

  • Métodos
    • Tipos de parámetros
    • Tipos de devolución
    • Otros aspectos relacionados con la firma, como excepciones arrojadas.
  • Genéricos

Echemos un vistazo a algunos ejemplos para comprender mejor los términos.

 class T class T2 extends T //Covariance: The return types of the method "method" have the same //direction of inheritance as the classes A and B. class A { T method() } class B extends A { T2 method() } //Contravariance: The parameter types of the method "method" have a //direction of inheritance opposite to the one of the classes A and B. class A { method(T2 t) } class B { method(T t) } 

En ambos casos, el “método” queda anulado. Además, los ejemplos anteriores son las únicas ocurrencias legales de Cov. y Contrav. en lenguajes orientados a objetos .

  • Covarianza – Tipos de devolución y declaraciones de excepción
  • Contravarianza – Parámetros de entrada
  • Invarianza – Parámetros de entrada y salida

Echemos un vistazo a algunos ejemplos de contador para comprender mejor la lista anterior:

 //Covariance of return types: OK class Monkey { Monkey clone() } class Human extends Monkey { Human clone() } Monkey m = new Human(); Monkey m2 = m.clone(); //You get a Human instance, which is ok, //since a Human is-a Monkey. //Contravariance of return types: NOT OK class Fruit class Orange extends Fruit class KitchenRobot { Orange make() } class Mixer extends KitchenRobot { Fruit make() } KitchenRobot kr = new Mixer(); Orange o = kr.make(); //Orange expected, but got a fruit (too general!) //Contravariance of parameter types: OK class Food class FastFood extends Food class Person { eat(FastFood food) } class FatPerson extends Person { eat(Food food) } Person p = new FatPerson(); p.eat(new FastFood()); //No problem: FastFood is-a Food, which FatPerson eats. //Covariance of parameter types: NOT OK class Person { eat(Food food) } class FatPerson extends Person { eat(FastFood food) } Person p = new FatPerson(); p.eat(new Food()); //Oops! FastFood expected, but got Food (too general). 

Este tema es tan sofisticado que podría continuar por mucho tiempo. Te aconsejo que revises Cov. y Contrav. de generics por ti mismo. Además, necesita saber cómo funciona el enlace dynamic para comprender completamente los ejemplos (qué métodos se llaman exactamente).

Los términos surgieron del principio de sustitución de Liskov, que define los criterios necesarios para modelar un tipo de datos como un subtipo de otro. Es posible que también desee investigarlo.

Según entiendo, no se trata de relaciones de subtipo que son co / contra variantes, sino más bien operaciones (o proyecciones) entre esos tipos (como delegates y generics). Por lo tanto:

 Animal someAnimal = new Giraffe(); 

no es covariante, sino que es solo compatibilidad de asignación ya que el tipo Jirafa es ‘más pequeño que’ el tipo Animal. Co / contravariancia se convierte en un problema cuando tiene alguna proyección entre estos tipos, como por ejemplo:

 IEnumerable giraffes = new[] { new Giraffe(); }; IEnumerable animals = giraffes; 

Esto no es válido en C # 3, sin embargo, debería ser posible ya que una secuencia de jirafas es una secuencia de animales. La proyección T -> IEnumerable conserva la ‘dirección’ de la relación de tipo desde Giraffe < Animal e IEnumerable < IEnumerable (tenga en cuenta que la asignación requiere que el tipo del lado izquierdo sea al menos igual ancho como el derecho).

Contra-varianza invierte la relación tipo:

 Action printAnimal = a => {System.Console.WriteLine(a.Name)}; Action printGiraffe = printAnimal; 

Esto tampoco es legal en C # 3, pero debería ser así, ya que cualquier acción que tome un animal puede hacer frente a que se le pase una jirafa. Sin embargo, dado que Giraffe < Animal and Action < Action la proyección ha invertido las relaciones tipo. Esto es legal en C # 4.

Entonces para responder las preguntas en tu ejemplo:

 //the following are neither covariant or contravariant - since there is no projection this is just assignment compatibility Mammal mammal1 = new Giraffe(); Mammal mammal2 = new Dolphin(); //compare is contravariant with respect to its arguments - //the delegate assignment is legal in C#4 but not in C#3 Func compare = (m1, m2) => //whatever Func c2 = compare; //always invalid - right hand side must be smaller or equal to left hand side Mammal mammal1 = new Animal(); //not valid for same reason - animal cannot be assigned to Mammal Compare(new Animal(), new Dolphin()); 

(Editado en respuesta a los comentarios)

Este artículo de MSDN sobre el tema describió la covarianza y la contravarianza tal como se aplica al emparejar una función con un delegado. Una variable del tipo de delegado:

 public delegate bool Compare(Giraffe giraffe, Dolphin dolphin); 

podría (debido a la contravariancia) ser llenado con la función:

 public bool Compare(Mammal mammal1, Mammal mammal2) { return String.Compare(mammal1.Name, mammal2.Name) == 0; } 

Según mi lectura, no tiene que ver con llamar a la función directamente, sino con funciones coincidentes con delegates. No estoy seguro de que pueda reducirse al nivel que demuestre, con variables individuales o asignaciones de objetos que son contravariantes o covariantes. Pero la asignación de un delegado usa contravarianza o covarianza de una manera que tenga sentido para mí de acuerdo con el artículo vinculado. Debido a que la firma del delegado contiene más tipos derivados que la instancia real, esto se conoce como “contravarianza”, algo separado de “covarianza” en el que el tipo de devolución de un delegado es menos derivado que la instancia real.

Mírelo de esta manera: si tengo una función func que trata sobre el subtipo Mamífero, de la forma Mamífero m = Func (g (Mamífero)) , puedo cambiar Mamífero por algo que incluya Mamífero , que aquí es el Animal Base.

En términos de una analogía deportiva para comprender la imagen de abajo, puedes atrapar una pelota con tus propias manos como en Cricket, pero también es posible (y más fácil) atrapar una pelota con los guantes de béisbol.

Lo que ves a la izquierda es la covarianza, lo que ves dentro de la parte del parámetro es la contravariancia.

enter image description here

Puede preguntarse “¿Por qué la curva verde izquierda es más grande que la curva roja? ¿No se supone que el Subtipo que generalmente hace más que el tipo base es más grande?” Respuesta: No. El tamaño del paréntesis denota la variedad de objetos permitidos, como un diagtwig de Venn. Un conjunto de mamíferos es más pequeño que un animal fijo. De manera similar, f (Mamífero) es más pequeño que f (Animal) ya que solo admite un conjunto de objetos más pequeño. (es decir, una función que maneja Mamíferos no manejará todos los Animales, pero una función que maneja Animales siempre puede manejar un Mamífero). Por lo tanto, la relación se invierte como f (animal) se puede pasar en lugar de f (mamífero) por lo que es contravariante.