2009-07-22 8 views
15

Quelqu'un peut-il m'expliquer, le concept de covariance et contravariance dans la théorie des langages de programmation?Covariance et contravariance dans les langages de programmation

+5

Je sens une question de devoirs. –

+0

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

+0

duplication possible de [C#: Variance (Covariance/Contravariance) un autre mot pour polymorphisme?] (http://stackoverflow.com/questions/1078423/c-sharp-is-variance-covariance-contravariance-another-word-for-polymorphis) – nawfal

Répondre

13

Covariance est assez simple et la meilleure idée de la perspective d'une classe de collection List. Nous pouvons paramétrer la classe List avec un paramètre de type T. Autrement dit, notre liste contient des éléments de type T pour certains T. Liste serait Covariant si

S est un sous-type de liste T ssi [S] est un sous-type de liste [T]

(où j'utilise la définition mathématique ssi signifie si et seulement si.)

C'est un List[Apple]est unList[Fruit]. S'il y a une routine qui accepte un List[Fruit] comme paramètre, et que j'ai un List[Apple], alors je peux le passer en paramètre valide.

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

Si notre classe de collection List est mutable, alors covariance n'a pas de sens, car on peut supposer que notre routine pourrait ajouter un autre fruit (qui n'a pas été une pomme) comme ci-dessus. Par conséquent, nous devrions seulement aimer immutables classes de collecte pour être covariant!

+0

Bonne définition, mais il manque le fait que non seulement les types peuvent être traités comme co/contravariant. Par exemple, Java 'List ' ne l'est pas non plus, mais les jokers Java vous permettent de traiter de façon covariante ou contravariante au point d'utilisation (plutôt que de déclarer) - bien sûr, en limitant l'ensemble des opérations sur le type à celles qui sont en fait covariant et contravariant pour cela. –

+1

Je crois que 'List 'est une sorte de * type existentiel *: c'est-à-dire' List [T] forSome T <: Fruit' - le * forSome T <: Fruit * est lui-même un type dans cette instance. Java n'est toujours pas covariant dans ce type. Par exemple une méthode acceptant un 'List 'ne pas accepter' List ' –

+0

Je veux dire" n'accepterait pas un 'Liste ' bien sûr –

14
+1

Ceci est une bonne explication, pas bookish.Merci –

+1

Heck - J'ai même apprécié de le lire.Plus de Wikipedia :) – xtofl

+0

Eh bien FWIW, l'explication provient d'une discussion que mes collègues et moi avons eue avec un membre éminent (ancien HP) lorsque la discussion a tourné vers OOSC par Bertrand Meyers dans laquelle je pense que Meyers souligne l'importance de la contravariance dans OOP. – Abhay

6

Voici mes articles sur la façon dont nous avons ajouté de nouvelles fonctionnalités variance à 4,0 C#. Commencez par le bas.

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

+0

OT: Eric, vous avez posté un puzzle sur l'une des questions plus tôt. Je n'ai pas pu le résoudre et j'ai posé des questions à ce sujet. Pourriez-vous s'il vous plaît le regarder? http://stackoverflow.com/questions/1167666/c-puzzle-reachable-goto-pointing-to-an-unreachable-label – SolutionYogi

+0

OMG Eric Lippert !!!! – Janie

+1

Regardez-le avec ces points d'exclamation. Vous pouvez mettre un oeil sur quelqu'un avec une de ces choses. –

0

Bart De Smet a une grande entrée de blog sur la covariance & contravariance here.

4
4

Il y a une distinction faite entre covariance et contravariance.
Très grossièrement, une opération est covariante si elle préserve l'ordre des types, et contravariante si elle inverse cet ordre.

La commande elle-même est destinée à représenter des types plus généraux plus grands que des types plus spécifiques.
Voici un exemple d'une situation où C# supporte la covariance. Tout d'abord, c'est un tableau d'objets:

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

Bien sûr, il est possible d'insérer des valeurs différentes dans le tableau, car à la fin, ils dérivent tous System.Object dans framework .Net. En d'autres termes, System.Object est un type très général ou grand. Maintenant, voici un endroit où covariance est prise en charge:
attribution d'une valeur d'un type plus petit à une variable d'un type plus grand

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

Les objets variables, qui est de type object[], peut stocker une valeur c'est en fait de type string[]. Pensez-y - jusqu'à un certain point, c'est ce que vous attendez, mais ce n'est pas le cas. Après tout, alors que string dérive de object, string[]NE DÉCRIT PAS dérive de object[]. Le support de la langue pour la covariance dans cet exemple rend l'assignation possible de toute façon, ce que vous trouverez dans de nombreux cas. La variante est une fonctionnalité qui rend le langage plus intuitif.

Les considérations autour de ces sujets sont extrêmement compliquées. Par exemple, en fonction du code précédent, voici deux scénarios qui entraîneront des erreurs. Un exemple pour le fonctionnement de la contravariance est un peu plus compliqué.Imaginez ces deux classes:

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

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

Woman est dérivé de Person, évidemment. Considérons maintenant que vous avez ces deux fonctions:

static void WorkWithPerson(Person person) { 
} 

static void WorkWithWoman(Woman woman) { 
} 

L'une des fonctions fait quelque chose (peu importe quoi) avec un Woman, l'autre est plus générale et peut fonctionner avec tout type dérivé de Person. Du côté Woman des choses, vous avez maintenant également:

delegate void AcceptWomanDelegate(Woman person); 

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

DoWork est une fonction qui peut prendre un Woman et une référence à une fonction qui prend aussi un Woman, puis il passe l'instance de Woman à le délégué. Considérez le polymorphisme des éléments que vous avez ici. Person est plus grand que Woman, et WorkWithPerson est plus grand que WorkWithWoman. WorkWithPerson est également considéré plus grand que AcceptWomanDelegate à des fins de variance.

Enfin, vous avez ces trois lignes de code:

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

Une instance Woman est créé. Puis DoWork est appelé, en passant dans l'instance Woman ainsi qu'une référence à la méthode WorkWithWoman. Ce dernier est évidemment compatible avec le type de délégué AcceptWomanDelegate - un paramètre de type Woman, aucun type de retour. La troisième ligne est un peu étrange, cependant. La méthode WorkWithPerson prend un Person comme paramètre, pas un Woman, comme requis par AcceptWomanDelegate. Néanmoins, WorkWithPerson est compatible avec le type délégué. Contravariance permet, dans le cas de délégués le plus grand type WorkWithPerson peut être stocké dans une variable du plus petit type AcceptWomanDelegate. Une fois de plus c'est la chose intuitive: si WorkWithPerson peut fonctionner avec n'importe quel Person, passant dans un Woman ne peut pas se tromper, non? À l'heure actuelle, vous vous demandez peut-être comment tout cela se rapporte aux génériques. La réponse est que la variance peut aussi être appliquée aux génériques. L'exemple précédent utilise les tableaux object et string. Ici, le code utilise des listes génériques au lieu des tableaux:

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

Si vous essayez ceci, vous constaterez que ce n'est pas un scénario pris en charge en C#. En C# version 4.0 ainsi que NET Framework 4.0, le soutien de la variance des génériques a été nettoyé, et il est maintenant possible d'utiliser les nouveaux mots-clés dans et sur avec des paramètres de type générique. Ils peuvent définir et restreindre la direction du flux de données pour un paramètre de type particulier, permettant à la variance de fonctionner.Mais dans le cas de List<T>, les données de type T circulent dans les deux sens - il existe des méthodes sur le type List<T> qui renvoient des valeurs T, et d'autres qui reçoivent de telles valeurs.

Le point de ces restrictions directionnelles est pour permettre la variance où il est logique, mais pour éviter les problèmes comme l'erreur d'exécution mentionné dans l'un des exemples précédents du tableau. Lorsque les paramètres de type sont décorées correctement dans ou sur, le compilateur peut vérifier, et autoriser ou non, sa variance à temps compilation. Microsoft est allé à l'effort d'ajouter ces mots-clés à de nombreuses interfaces standard dans framework .Net, comme IEnumerable<T>:

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

Pour cette interface, le flux de données de type T objets est clair: ils ne peuvent jamais être récupérées à partir des méthodes prises en charge par cette interface, qui ne leur ont pas été transmises. En conséquence, il est possible de construire un exemple similaire à la List<T> tentative décrite précédemment, mais en utilisant IEnumerable<T>:

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

Ce code est acceptable pour le compilateur C# depuis la version 4.0, car IEnumerable<T> est covariante en raison de la sur spécificateur sur le paramètre de type T. Lorsque vous travaillez avec des types génériques, il est important d'être conscient de la variance et de la manière dont le compilateur applique différents types de tromperie pour que votre code fonctionne comme vous le souhaitez.

Il y a plus à connaître sur la variance que ce qui est couvert dans ce chapitre, mais cela suffira pour rendre tout autre code compréhensible.

Ref:

0

C# et le CLR permettent de covariance et contre-variance des types de référence lorsque liant une méthode à un délégué. Covariance signifie qu'une méthode peut renvoyer un type dérivé du type de retour du délégué. La contra-variance signifie qu'une méthode peut prendre un paramètre qui est une base du type de paramètre du délégué. Par exemple, étant donné qu'un délégué est défini comme suit:

delegate Object MyCallback (FileStream s);

il est possible de construire une instance de ce type délégué lié à une méthode qui est prototypée

comme ceci:

Chaîne SomeMethod (courant s);

Ici, le type de retour de SomeMethod (String) est un type dérivé du type de retour du délégué (Object); cette covariance est autorisée.Le type de paramètre de SomeMethod (Stream) est un type qui est une classe de base du type de paramètre du délégué (FileStream); cette contre-variance est autorisée.

Notez que la covariance et la contre-variance sont prises en charge uniquement pour les types de référence, pas pour les types de valeur ou pour les vides. Ainsi, par exemple, je ne peux pas lier la méthode suivante au délégué MyCallback:

Int32 SomeOtherMethod (Streams);

Même si le type de retour de SomeOtherMethod (Int32) est dérivé du type retour de MyCallback (Object), cette forme de covariance n'est pas autorisé parce que Int32 est un type de valeur.

De toute évidence, la raison pour laquelle les types de valeur et non avenue ne peuvent pas être utilisés pour covariance et contre-variance est parce que la structure de mémoire pour ces choses varie, alors que la structure de la mémoire pour les types de référence est toujours un pointeur. Heureusement, le compilateur C# produira une erreur si vous essayez de faire quelque chose qui n'est pas supporté.

Questions connexes