44 votes

Contravariance expliquée

D'abord, j'ai lu beaucoup d'explications sur DONC et les blogs sur la covariance et la contravariance et un merci à Eric Lippert pour produire de telles une grande série sur la Covariance et la Contravariance.

Toutefois, j'ai une question plus précise que je suis en train d'essayer d'obtenir ma tête autour de un peu.

Autant je comprends par Eric explication est que la Covariance et la Contravariance sont les deux adjectifs qui décrivent une transformation. Covariante de transformation est celle qui conserve l'ordre des types et Contravariant de la transformation est l'un que l'inverse.

Je comprends la covariance de telle manière que je pense que la plupart des développeurs de comprendre intuitivement.

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

L'opération de retour ici est covariant que nous sommes en préservant la taille à laquelle les deux Animaux est encore plus grand que les Mammifères ou la Girafe. Sur cette note, la plupart des opérations de retour sont covariants, contravariant opérations n'aurait aucun sens.

  //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();

Ce morceau de code n'aurait pas de sens pour la plupart des développeurs.

Ma confusion réside dans Contravariant argument paramètres. Si vous aviez une méthode telle que

bool Compare(Mammal mammal1, Mammal mammal2);

J'ai toujours appris que les paramètres d'entrée toujours la force contravariant comportement. De sorte que si le type est utilisé comme paramètre d'entrée son comportement doit être contravariant.

Cependant quelle est la différence entre le code suivant

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?

De la même manière que vous ne pouvez pas faire quelque chose comme cela, vous ne pouvez pas le faire

   //not valid
   Mammal mammal1 = new Animal();

   //not valid
   Compare(new Animal(), new Dolphin());

Je suppose que ce que je demande est, ce qui fait un argument de méthode passage d'un contravariant de la transformation.

Désolé pour le long post, je suis peut-être comprendre ce tort.

EDIT:

Par une conversation ci-dessous, je comprends que, par exemple à l'aide d'un délégué de la couche peut clairement montrer la contravariance. Considérons l'exemple suivant

//legal, covariance
Mammal someMammal = new Mammal();
Animal someAnimal = someMammal;

// legal in C# 4.0, covariance (because defined in Interface)
IEnumerable<Mammal> mammalList = Enumerable.Empty<Mammal>();
IEnumerable<Animal> animalList = mammalList;

//because of this, one would assume
//that the following line is legal as well

void ProcessMammal(Mammal someMammal);

Action<Mammal> processMethod = ProcessMammal;
Action<Animal> someAction = processMethod;

Bien sûr que c'est illégal parce que quelqu'un peut passer n'importe quel Animal à uneaction, où, comme le ProcessMammal attend quoi c'est de Mammifères ou plus spécifique ( à moins que l'un Mammifère ). C'est pourquoi uneaction doit seulement être une Action ou quelque chose de plus spécifique (Action)

Cependant, c'est l'introduction d'une couche de délégués dans le milieu, est-il nécessaire que pour une contravariant de projection à arriver, il y a un délégué dans le milieu? Et si nous devions définir comme une interface de nous déclarer le paramètre argument comme un contravariant type seulement parce que nous ne voulons pas de quelqu'un pour être en mesure de faire ce que j'avais indiqué ci-dessus avec les délégués?

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

31voto

Alexandra Rusina Points 6063

Mise À Jour: Ooops. Comme il s'est avéré, j'ai mélangé de la variance et de la "cession de compatibilité" dans ma première réponse. Modifié la réponse en conséquence. Aussi j'ai écrit un post de blog qui, je l'espère, devrait répondre à ces questions, mieux: la Covariance et la Contravariance FAQ

Réponse: je suppose que la réponse à votre première question, c'est que vous n'avez pas de contravariance dans cet exemple:

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

En outre, vous n'avez même pas de covariance ici. Ce que vous avez appelé "la compatibilité d'affectation", ce qui signifie que vous pouvez toujours affecter une instance d'un plus type dérivé à une instance d'un type dérivé.

En C#, la variance est pris en charge pour les tableaux, les délégués et les interfaces génériques. Comme Eric Lippert a dit dans son blog Quelle est la différence entre la covariance et de la compatibilité d'affectation? c'est qu'il est préférable de penser à propos de la variance "projection" de types.

La Covariance est facile à comprendre, car il suit l'attribution de la compatibilité des règles (tableau d'une plus type dérivé peut être attribué à un tableau d'un type dérivé, "object[] objs = new string[10];"). La Contravariance renverse ces règles. Par exemple, imaginez que vous pourriez faire quelque chose comme "string[] chaînes = new object[10];". Bien sûr, vous ne pouvez pas le faire pour des raisons évidentes. Mais que serait la contravariance (mais encore une fois, les tableaux ne sont pas contravariant, ils prennent en charge la covariance seulement).

Voici quelques exemples à partir de MSDN qui, je l'espère, va vous montrer ce qu'contravariance vraiment les moyens (je possède ces documents maintenant, donc si vous pensez que quelque chose n'est pas clair dans la doc, n'hésitez pas à me donner des commentaires):

  1. À l'aide de la Variance dans les Interfaces Génériques des Collections

    Employee[] employees = new Employee[3];
    // You can pass PersonComparer, 
    // which implements IEqualityComparer<Person>,
    // although the method expects IEqualityComparer<Employee>.
    IEnumerable<Employee> noduplicates =
        employees.Distinct<Employee>(new PersonComparer());
    
  2. À l'aide de la Variance dans Délégués

    // 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. À l'aide de la Variance pour la touche Func et l'Action Générique Délégués

     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<Employee> addEmployeeToContacts = AddToContacts;
    

Espérons que cette aide.

19voto

Dave O. Points 1389

La Covariance et la Contravariance ne sont pas des choses que vous pouvez observer lors de l'instanciation des classes. Ainsi, il est faux de parler de l'un d'eux lorsque l'on regarde une simple instanciation de classe, comme dans votre exemple: Animal someAnimal = new Giraffe();

Ces conditions ne sont pas classés en opérations. Les termes de Covariance, la Contravariance et d'Invariance de décrire la relation entre certains aspects de classes et de leurs sous-classes.

La Covariance
signifie qu'un change d'aspect similaire à la direction de l'héritage.
La Contravariance
signifie qu'un aspect des changements à l'opposé de la direction de l'héritage.
Invariance
signifie que l'aspect ne change pas d'une classe à ses sous-classe(es).

En général, nous l'égard des aspects suivants, lorsque l'on parle de Cov., Contrav. et Inv.:

  • Méthodes
    • Les types de paramètre
    • Les types de retour
    • D'autres signatures liées à des aspects tels que les exceptions lancées.

  • Les génériques

Laissez-nous jeter un oeil à quelques exemples afin d'avoir une meilleure compréhension des termes.

//covariant operation

Dans les deux cas, la "méthode" devient surchargé! En outre, les exemples ci-dessus sont les seuls juridique occurrences de Cov. et Contrav. dans les langages orientés objets.:

  • Covariance - types de Retour et l'exception des rapports de jet
  • La Contravariance - paramètres d'Entrée
  • L'Invariance d'Entrée et les paramètres de Sortie

Laissez-nous jeter un oeil à quelques contre-exemples pour mieux comprendre la liste ci-dessus:

Ce sujet est tellement sophistiqué, que je pourrais continuer pendant un temps très long. Je vous conseille de vérifier les Cov. et Contrav. des Génériques par vous-même. En outre, vous devez savoir comment les dynamiques de la liaison fonctionne pour comprendre pleinement les exemples (méthodes à obtenir exactement appelé).

Les termes se pose à partir du principe de substitution de Liskov, qui définit les critères nécessaires pour la modélisation d'un type de données comme un sous-type d'un autre. Vous pouvez également enquêter sur elle.

10voto

Lee Points 63849

Ma compréhension est qu'il n'est pas sous-type de relations qui sont co/contra-variante mais plutôt des opérations (ou projections) entre ces deux types (tels que des délégués et génériques). Donc:

Animal someAnimal = new Giraffe();

n'est pas co-variant, mais plutôt c'est juste la compatibilité d'affectation puisque le type Girafe est "plus petit que" le type d'Animal. Co/contra-variance devient un problème lorsque vous avez une projection entre ces types, tels que:

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

Ce n'est pas valide en C#3, mais il devrait être possible depuis une séquence de girafes est une séquence d'animaux. La projection T -> IEnumerable<T> préserve la 'direction' de la relation de type depuis Giraffe < Animal et IEnumerable<Giraffe> < IEnumerable<Animal> (à noter que l'affectation nécessite que le type de la gauche est au moins aussi large que le droit).

Contra-variance inverse la relation de type:

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

Ce n'est pas non juridique en C#3, mais il devrait être depuis toute action de prendre un animal peut supporter d'être passé d'une Girafe. Cependant, depuis Giraffe < Animal et Action<Animal> < Action<Giraffe> de la projection a permis d'inverser le type de relations. C'est légal en C#4.

Donc, pour répondre aux questions dans votre exemple:

//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<Mammal, Mammal, bool> compare = (m1, m2) => //whatever
Func<Giraffe, Dolphin, bool> 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());

1voto

BlueMonkMN Points 10838

(Édité en réponse aux commentaires)

Cet article MSDN sur le sujet décrit la covariance et la contravariance tel qu'il s'applique à la contrepartie d'une fonction à un délégué. Une variable de type délégué:

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

pourrait (à cause de la contravariance) être rempli par la fonction:

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

D'après mes lectures, il n'a pas à faire avec l'appel de la fonction directement, mais les fonctions de correspondance avec les délégués. Je ne suis pas sûr qu'il peut être bouilli vers le bas pour le niveau de vous démontrer, avec les variables individuelles ou de l'objet d'affectations d'être contravariant ou covariants. Mais l'attribution d'un délégué utilise la contravariance ou de covariance dans une manière qui fait sens pour moi selon l'article lié. Parce que le délégué de la signature contient plus de types dérivés de l'instance elle-même, c'est dénommé "la contravariance", quelque chose de séparé de "covariance", dans lequel un délégué du type de retour est moins dérivé de l'instance réelle.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X