2009-07-22 54 views
15

¿Alguien me puede explicar el concepto de covarianza y contravarianza en la teoría de los lenguajes de programación ?Covarianza y contravarianza en lenguajes de programación

+5

Huelo una pregunta de tarea. –

+0

[covarianza vs contravarianza] (http://izlooite.blogspot.com/2011/04/covariance-and-contravariance.html) –

+0

posible duplicado de [C#: ¿Es la varianza (covarianza/contravarianza) otra palabra para el polimorfismo?] (http://stackoverflow.com/questions/1078423/c-sharp-is-variance-covariance-contravariance-another-word-for-polymorphis) – nawfal

Respuesta

13

covarianza es bastante simple y mejor pensamiento desde la perspectiva de alguna clase de colección List. Podemos parametrizar la clase List con algún tipo de parámetro T. Es decir, nuestra lista contiene elementos del tipo T para algunos T. Lista se covariante si

S es un subtipo de la lista t si y sólo si [S] es un subtipo de lista [T]

(¿Dónde estoy usando la definición matemática si y sólo si el sentido de si y sólo si.)

es decir, un List[Apple]es unaList[Fruit]. Si hay alguna rutina que acepta un List[Fruit] como parámetro, y tengo un List[Apple], entonces puedo pasar esto como un parámetro válido.

def something(l: List[Fruit]) { 
    l.add(new Pear()) 
} 

Si nuestra clase de colección List es mutable, a continuación, la covarianza no tiene sentido porque podríamos suponer que nuestra rutina podría añadir alguna otra fruta (que no era una manzana) que el anterior. Por lo tanto, solo nos gustaría que clases de colección inmutables sean covariantes.

+0

buena definición, pero se echa de menos el hecho de que no sólo los tipos pueden ser tratados como co/contravariante Por ejemplo, Java 'List ' tampoco lo es, pero los comodines de Java le permiten tratar de forma covariante o contravariante en el punto de uso (en lugar de declarar), por supuesto, restringir el conjunto de operaciones del tipo a aquellas que están realmente covariante y contravariante para él. –

+1

Creo que 'List 'es una especie de * tipo existencial *: es decir' List [T] forSome T <: Fruit' - the * forSome T <: Fruit * es en sí mismo un tipo en esta instancia. Sin embargo, Java todavía no es covariante en este tipo. Por ejemplo, un método que acepta una 'Lista 'no aceptaría' List ' –

+0

quiero decir "no aceptaría un 'Lista ' por supuesto –

14
+1

Esta es una buena explicación, no libresca Gracias –

+1

de Heck -.. Incluso disfrutado de su lectura Más de Wikipedia de :) – xtofl

+0

. Bueno, FWIW, la explicación proviene de una discusión que mis colegas y yo tuvimos con un miembro eminente (antiguo HP) cuando la discusión se desvió hacia OOSC por Bertrand Meyers en la que creo que Meyers destaca la importancia de la contravarianza en OOP. Nos dijo OOSC por B.Meyers es una lectura libro si se desea comprender prácticamente POO – Abhay

6

Aquí están mis artículos sobre cómo hemos añadido nuevas características varianza de C# 4.0. Comienza desde abajo.

http://blogs.msdn.com/ericlippert/archive/tags/Covariance+and+Contravariance/default.aspx

+0

OT: Eric, has publicado un acertijo en una de las preguntas anteriores. No pude resolverlo y le hice preguntas al respecto. ¿Podrías mirarlo? http://stackoverflow.com/questions/1167666/c-puzzle-reachable-goto-pointing-to-an-unreachable-label – SolutionYogi

+0

OMG Eric Lippert !!!! – Janie

+1

Véalo con esos signos de admiración allí. Puedes sacarle el ojo a alguien con una de esas cosas. –

0

Bart De Smet tiene una gran entrada de blog acerca de covarianza & contravarianza here.

4
4

Hay una distinción entre covarianza y contravarianza.
Muy aproximadamente, una operación es covariante si conserva el orden de los tipos, y es contravariante si invierte este orden.

El orden en sí está destinado a representar tipos más generales como más grandes que tipos más específicos.
Aquí hay un ejemplo de una situación en la que C# admite la covarianza. En primer lugar, esto es una matriz de objetos:

object[] objects=new object[3]; 
objects[0]=new object(); 
objects[1]="Just a string"; 
objects[2]=10; 

Por supuesto, es posible insertar diferentes valores en la matriz porque al final todos ellos se derivan de System.Object en marco .NET. En otras palabras, System.Object es un tipo muy grande o grande. Ahora, aquí hay un punto donde se apoya la covarianza:
la asignación de un valor de un tipo más pequeño de una variable de un tipo más grande

string[] strings=new string[] { "one", "two", "three" }; 
objects=strings; 

Los objetos variables, que es de tipo object[], puede almacenar un valor De hecho, es del tipo string[].

Piénselo - hasta cierto punto, es lo que espera, pero de nuevo no lo es. Después de todo, mientras string deriva de object, string[]NO deriva de object[]. El soporte de idiomas para la covarianza en este ejemplo hace que la asignación sea posible de todos modos, que es algo que encontrará en muchos casos. La variación es una función que hace que el lenguaje funcione de manera más intuitiva.

Las consideraciones sobre estos temas son extremadamente complicadas. Por ejemplo, basado en el código anterior, aquí hay dos escenarios que generarán errores.

// Runtime exception here - the array is still of type string[], 
// ints can't be inserted 
objects[2]=10; 

// Compiler error here - covariance support in this scenario only 
// covers reference types, and int is a value type 
int[] ints=new int[] { 1, 2, 3 }; 
objects=ints; 

Un ejemplo para el funcionamiento de la contravarianza es un poco más complicado.Imagínese estos dos clases:

public partial class Person: IPerson { 
    public Person() { 
    } 
} 

public partial class Woman: Person { 
    public Woman() { 
    } 
} 

Woman se deriva de Person, obviamente. Consideremos ahora usted tiene estas dos funciones:

static void WorkWithPerson(Person person) { 
} 

static void WorkWithWoman(Woman woman) { 
} 

Una de las funciones hace algo (no importa qué) con un Woman, el otro es más general y puede trabajar con cualquier tipo derivado de Person. En el lado Woman de las cosas, que ahora también tiene estos datos:

delegate void AcceptWomanDelegate(Woman person); 

static void DoWork(Woman woman, AcceptWomanDelegate acceptWoman) { 
    acceptWoman(woman); 
} 

DoWork es una función que puede tomar un Woman y una referencia a una función que también tiene un Woman, y luego pasa la instancia de Woman a el delegado Considere el polimorfismo de los elementos que tiene aquí. Person es mayor de Woman y WorkWithPerson es mayor de WorkWithWoman. WorkWithPerson también se considera más grande que AcceptWomanDelegate con el propósito de varianza.

Por último, tienes estas tres líneas de código: Se crea

Woman woman=new Woman(); 
DoWork(woman, WorkWithWoman); 
DoWork(woman, WorkWithPerson); 

Un ejemplo Woman. Luego se llama a DoWork, pasando en la instancia Woman, así como una referencia al método WorkWithWoman. El último es obviamente compatible con el tipo de delegado AcceptWomanDelegate - un parámetro del tipo Woman, sin tipo de devolución. La tercera línea es un poco extraña, sin embargo. El método WorkWithPerson toma un Person como parámetro, no un Woman, según lo requerido por AcceptWomanDelegate. Sin embargo, WorkWithPerson es compatible con el tipo de delegado. Contravarianza lo hace posible, por lo que en el caso de los delegados, el tipo más grande WorkWithPerson se puede almacenar en una variable del tipo más pequeño AcceptWomanDelegate. Una vez más es la cosa intuitiva: si WorkWithPerson puede trabajar con cualquier Person, pasando en un Woman no pueden estar equivocados, ¿verdad?

Por ahora, usted puede preguntarse cómo todo esto se relaciona con los genéricos. La respuesta es que la varianza también se puede aplicar a los genéricos. El ejemplo anterior usó matrices object y string. Aquí el código utiliza listas genéricas en lugar de las matrices:

List<object> objectList=new List<object>(); 
List<string> stringList=new List<string>(); 
objectList=stringList; 

Si intenta esto, se dará cuenta de que esto no es un escenario admitido en C#. En C# versión 4.0, así como .NET Framework 4.0, soporte de la varianza en los genéricos ha sido limpiado, y ahora es posible utilizar las nuevas palabras clave en y cabo con parámetros de tipo genérico. Pueden definir y restringir la dirección del flujo de datos para un parámetro de tipo particular, permitiendo que la varianza funcione.Pero en el caso de List<T>, los datos del tipo T fluyen en ambas direcciones; hay métodos en el tipo List<T> que devuelven los valores T, y otros que reciben dichos valores.

El punto de estas restricciones direccionales es para permitir varianza donde tiene sentido, pero a prevenir problemas como el error de ejecución mencionado en uno de los ejemplos de matriz anteriores. Cuando los parámetros de tipo están decoradas correctamente con en o a cabo, el compilador puede comprobar, y permitir o no, su varianza en tiempo de compilación. Microsoft ha hecho el esfuerzo de añadir estas palabras clave para muchas interfaces estándar en el marco .Net, como IEnumerable<T>:

public interface IEnumerable<out T>: IEnumerable { 
    // ... 
} 

Por esta interfaz, el flujo de datos de tipo T objetos es clara: pueden ser solamente siempre recuperado de los métodos compatibles con esta interfaz, no pasó a ellos. Como resultado, es posible construir un ejemplo similar al intento List<T> descrito anteriormente, pero utilizando IEnumerable<T>:

IEnumerable<object> objectSequence=new List<object>(); 
IEnumerable<string> stringSequence=new List<string>(); 
objectSequence=stringSequence; 

Este código es aceptable para el compilador de C# desde la versión 4.0 porque IEnumerable<T> es covariante debido a la cabo especificador en el parámetro de tipo T.

Al trabajar con tipos genéricos, es importante tener en cuenta la varianza y la forma en que el compilador aplica varios tipos de engaños para hacer que su código funcione de la manera que usted espera.

Hay más información sobre la varianza de la que se trata en este capítulo, pero esto será suficiente para que todos los demás códigos sean comprensibles.

Ref:

0

Tanto C# y el CLR permite la covarianza y contra-varianza de los tipos de referencia cuando de unión a un método para un delegado. Covarianza significa que un método puede devolver un tipo que es derivado del tipo de devolución del delegado. Contra-varianza significa que un método puede tomar un parámetro que es una base del tipo de parámetro del delegado. Por ejemplo, dado un delegado definido de la siguiente manera:

delegar el objeto MyCallback (FileStream s);

es posible construir una instancia de este tipo de delegado unido a un método que se prototipo

como esto:

cadena SomeMethod (Corriente s);

Aquí, el tipo de devolución de SomeMethod (Serie) es un tipo que se deriva del tipo de devolución del delegado (Objeto); esta covarianza está permitidaEl tipo de parámetro de SomeMethod (Stream) es un tipo que es una clase base del tipo de parámetro del delegado (FileStream); esta contra-varianza está permitida.

Tenga en cuenta que la covarianza y contra-varianza son compatibles sólo para los tipos de referencia, no para los tipos de valor o de vacío. Por lo tanto, por ejemplo, no puedo vincular el siguiente método al delegado de MyCallback:

Int32 SomeOtherMethod (Stream s);

A pesar de que de SomeOtherMethod tipo de retorno (Int32) se deriva de de MyCallback tipo de retorno (Objeto), esta forma de covarianza no está permitido porque Int32 es un tipo de valor.

Obviamente, la razón por la cual los tipos de valor y vacío no se pueden utilizar para covarianza y contra-varianza es debido a que la estructura de memoria para estas cosas varía, mientras que la estructura memoria para los tipos de referencia es siempre un puntero. Afortunadamente, el compilador C# producirá un error si intenta hacer algo que no es compatible.

Cuestiones relacionadas