Covariance et contravariance exemple du monde réel

J’ai un peu de mal à comprendre comment j’utiliserais la covariance et la contravariance dans le monde réel.

Jusqu’à présent, les seuls exemples que j’ai vus étaient le même exemple de vieux tableau.

object[] objectArray = new ssortingng[] { "ssortingng 1", "ssortingng 2" }; 

Ce serait bien de voir un exemple qui me permettrait de l’utiliser pendant mon développement si je pouvais le voir être utilisé ailleurs.

Disons que vous avez une personne de classe et une classe qui en découle, Maître. Vous avez des opérations qui prennent un IEnumerable comme argument. Dans votre classe, vous avez une méthode qui renvoie un IEnumerable . La covariance vous permet d’utiliser directement ce résultat pour les méthodes qui utilisent un IEnumerable , en substituant un type plus dérivé à un type moins dérivé (plus générique). La contre-variance, de manière contre-intuitive, vous permet d’utiliser un type plus générique, où un type plus dérivé est spécifié. Voir aussi https://msdn.microsoft.com/en-us/library/dd799517.aspx

 public class Person { public ssortingng Name { get; set; } } public class Teacher : Person { } public class MailingList { public void Add(IEnumerable people) { ... } } public class School { public IEnumerable GetTeachers() { ... } } public class PersonNameComparer : IComparer { public int Compare(Person a, Person b) { if (a == null) return b == null ? 0 : -1; return b == null ? 1 : Compare(a,b); } private int Compare(ssortingng a, ssortingng b) { if (a == null) return b == null ? 0 : -1; return b == null ? 1 : a.CompareTo(b); } } ... var teachers = school.GetTeachers(); var mailingList = new MailingList(); // Add() is covariant, we can use a more derived type mailingList.Add(teachers); // the Set constructor uses a contravariant interface, IComparer, // we can use a more generic type than required. See https://msdn.microsoft.com/en-us/library/8ehhxeaf.aspx for declaration syntax var teacherSet = new SortedSet(teachers, new PersonNameComparer()); 
 // Contravariance interface IGobbler { void gobble(T t); } // Since a QuadrupedGobbler can gobble any four-footed // creature, it is OK to treat it as a donkey gobbler. IGobbler dg = new QuadrupedGobbler(); dg.gobble(MyDonkey()); // Covariance interface ISpewer { T spew(); } // A MouseSpewer obviously spews rodents (all mice are // rodents), so we can treat it as a rodent spewer. ISpewer rs = new MouseSpewer(); Rodent r = rs.spew(); 

Pour être complet…

 // Invariance interface IHat { void hide(T t); T pull(); } // A RabbitHat… IHat rHat = RabbitHat(); // …cannot be treated covariantly as a mammal hat… IHat mHat = rHat; // Comstackr error // …because… mHat.hide(new Dolphin()); // Hide a dolphin in a rabbit hat?? // It also cannot be treated contravariantly as a cottontail hat… IHat cHat = rHat; // Comstackr error // …because… rHat.hide(new MarshRabbit()); cHat.pull(); // Pull a marsh rabbit out of a cottontail hat?? 

Voici ce que j’ai mis en place pour m’aider à comprendre la différence

 public interface ICovariant { } public interface IContravariant { } public class Covariant : ICovariant { } public class Contravariant : IContravariant { } public class Fruit { } public class Apple : Fruit { } public class TheInsAndOuts { public void Covariance() { ICovariant fruit = new Covariant(); ICovariant apple = new Covariant(); Covariant(fruit); Covariant(apple); //apple is being upcasted to fruit, without the out keyword this will not comstack } public void Contravariance() { IContravariant fruit = new Contravariant(); IContravariant apple = new Contravariant(); Contravariant(fruit); //fruit is being downcasted to apple, without the in keyword this will not comstack Contravariant(apple); } public void Covariant(ICovariant fruit) { } public void Contravariant(IContravariant apple) { } } 

tldr

 ICovariant apple = new Covariant(); //because it's covariant IContravariant fruit = new Contravariant(); //because it's contravariant 

Les mots-clés in et out contrôlent les règles de conversion du compilateur pour les interfaces et les delegates avec des parameters génériques:

 interface IInvariant { // This interface can not be implicitly cast AT ALL // Used for non-readonly collections IList GetList { get; } // Used when T is used as both argument *and* return type T Method(T argument); }//interface interface ICovariant { // This interface can be implicitly cast to LESS DERIVED (upcasting) // Used for readonly collections IEnumerable GetList { get; } // Used when T is used as return type T Method(); }//interface interface IContravariant { // This interface can be implicitly cast to MORE DERIVED (downcasting) // Usually means T is used as argument void Method(T argument); }//interface class Casting { IInvariant invariantAnimal; ICovariant covariantAnimal; IContravariant contravariantAnimal; IInvariant invariantFish; ICovariant covariantFish; IContravariant contravariantFish; public void Go() { // NOT ALLOWED invariants do *not* allow implicit casting: invariantAnimal = invariantFish; invariantFish = invariantAnimal; // NOT ALLOWED // ALLOWED covariants *allow* implicit upcasting: covariantAnimal = covariantFish; // NOT ALLOWED covariants do *not* allow implicit downcasting: covariantFish = covariantAnimal; // NOT ALLOWED contravariants do *not* allow implicit upcasting: contravariantAnimal = contravariantFish; // ALLOWED contravariants *allow* implicit downcasting contravariantFish = contravariantAnimal; }//method }//class // .NET Framework Examples: public interface IList : ICollection, IEnumerable, IEnumerable { } public interface IEnumerable : IEnumerable { } class Delegates { // When T is used as both "in" (argument) and "out" (return value) delegate T Invariant(T argument); // When T is used as "out" (return value) only delegate T Covariant(); // When T is used as "in" (argument) only delegate void Contravariant(T argument); // Confusing delegate T CovariantBoth(T argument); // Confusing delegate T ContravariantBoth(T argument); // From .NET Framework: public delegate void Action(T obj); public delegate TResult Func(T arg); }//class 
 class A {} class B : A {} public void SomeFunction() { var someListOfB = new List(); someListOfB.Add(new B()); someListOfB.Add(new B()); someListOfB.Add(new B()); SomeFunctionThatTakesA(someListOfB); } public void SomeFunctionThatTakesA(IEnumerable input) { // Before C# 4, you couldn't pass in List: // cannot convert from // 'System.Collections.Generic.List' to // 'System.Collections.Generic.IEnumerable' } 

Fondamentalement, chaque fois que vous avez une fonction qui prend un Enumerable d’un type, vous ne pouvez pas transmettre un Enumerable d’un type dérivé sans le convertir explicitement.

Juste pour vous avertir d’un piège cependant:

 var ListOfB = new List(); if(ListOfB is IEnumerable) { // In C# 4, this branch will // execute... Console.Write("It is A"); } else if (ListOfB is IEnumerable) { // ...but in C# 3 and earlier, // this one will execute instead. Console.Write("It is B"); } 

C’est du code horrible de toute façon, mais il existe et le changement de comportement dans C # 4 pourrait introduire des bogues subtils et difficiles à trouver si vous utilisez une construction comme celle-ci.

Voici un exemple simple utilisant une hiérarchie d’inheritance.

Étant donné la hiérarchie de classes simple:

  Giraffe / LifeForm < - Animal <- \ Zebra 

Dans du code:

 public abstract class LifeForm { } public abstract class Animal : LifeForm { } public class Giraffe : Animal { } public class Zebra : Animal { } 

Invariance (Générique avec type paramétré décoré ni in out ni en out )

Apparemment, une telle méthode

 public static void PrintLifeForms(IList lifeForms) { foreach (var lifeForm in lifeForms) { Console.WriteLine(lifeForm.GetType().ToSsortingng()); } } 

... devrait accepter une collection hétérogène: (ce qu'elle fait)

 var myAnimals = new List { new Giraffe(), new Zebra() }; PrintLifeForms(myAnimals); // Giraffe, Zebra 

Cependant, le passage d'une collection d'un type plus dérivé échoue!

 var myGiraffes = new List { new Giraffe(), // "Jerry" new Giraffe() // "Melman" }; PrintLifeForms(myGiraffes); // Comstack Error! 

cannot convert from 'System.Collections.Generic.List' to 'System.Collections.Generic.IList'

Pourquoi? Le paramètre générique IList n'étant pas covariant - IList est invariant et n'accepte que les collections (implémentant IList) où le type paramétré T doit être LifeForm .

Si je modifie par malveillance l'implémentation de la méthode PrintLifeForms (mais laisse la même signature de méthode), la raison pour laquelle le compilateur empêche le passage de la List devient évidente:

  public static void PrintLifeForms(IList lifeForms) { lifeForms.Add(new Zebra()); } 

Comme IList permet d'append ou de supprimer des éléments, toute sous-classe de LifeForm pourrait donc être ajoutée au paramètre lifeForms et violerait le type de toute collection de types dérivés transmis à la méthode. (Ici, la méthode malveillante tenterait d’append un Zebra à var myGiraffes ). Heureusement, le compilateur nous protège de ce danger.

Covariance (générique avec type paramétré décoré avec out )

La covariance est largement utilisée avec les collections immuables (c'est-à-dire que de nouveaux éléments ne peuvent pas être ajoutés ou supprimés d'une collection)

La solution à l'exemple ci-dessus consiste à s'assurer qu'un type générique covariant est utilisé, par exemple IEnumerable (défini comme IEnumerable ). Cela empêche toute modification de la collection et, par conséquent, toute collection avec un sous-type de LifeForm peut maintenant être transmise à la méthode:

 public static void PrintLifeForms(IEnumerable lifeForms) { foreach (var lifeForm in lifeForms) { Console.WriteLine(lifeForm.GetType().ToSsortingng()); } } 

PrintLifeForms() peut maintenant être appelé avec des Zearm , des Giraffes et tout IEnumerable<> de toute sous-classe de LifeForm

Contravariance (générique avec type paramétré décoré avec in )

La contre-variance est fréquemment utilisée lorsque des fonctions sont passées en tant que parameters.

Voici un exemple de fonction qui prend une Action comme paramètre et l'invoque sur une instance connue d'un Zebra:

 public void PerformZebraAction(Action zebraAction) { var zebra = new Zebra(); zebraAction(zebra); } 

Comme prévu, cela fonctionne très bien:

 var myAction = new Action(z => Console.WriteLine("I'm a zebra")); PerformZebraAction(myAction); // I'm a zebra 

Intuitivement, cela échouera:

 var myAction = new Action(g => Console.WriteLine("I'm a giraffe")); PerformZebraAction(myAction); 

cannot convert from 'System.Action' to 'System.Action'

Cependant, cela réussit

 var myAction = new Action(a => Console.WriteLine("I'm an animal")); PerformZebraAction(myAction); // I'm an animal 

et même cela réussit aussi:

 var myAction = new Action(a => Console.WriteLine("I'm an amoeba")); PerformZebraAction(myAction); // I'm an amoeba 

Pourquoi? Parce que l' Action est définie comme Action , c'est-à-dire qu'elle est contravariant .

Bien que cela ne soit pas intuitif au début (par exemple, comment une Action peut-elle être passée en paramètre nécessitant Action ?), Vous remarquerez que la fonction appelée ( PerformZebraAction ) est responsable pour transmettre des données (dans ce cas, une instance Zebra ) à la fonction, les données ne proviennent pas du code appelant.

En raison de l'approche inversée consistant à utiliser les fonctions d'ordre supérieur de cette manière, au moment où l' Action est appelée, c'est l'instance d'object la plus dérivée qui est appelée contre la fonction zebraAction (passée en paramètre), qui utilise elle-même le type moins dérivé. .

À partir de MSDN

L’exemple de code suivant montre la prise en charge de covariance et de contravariance pour les groupes de méthodes

 static object GetObject() { return null; } static void SetObject(object obj) { } static ssortingng GetSsortingng() { return ""; } static void SetSsortingng(ssortingng str) { } static void Test() { // Covariance. A delegate specifies a return type as object, // but you can assign a method that returns a ssortingng. Func del = GetSsortingng; // Contravariance. A delegate specifies a parameter type as ssortingng, // but you can assign a method that takes an object. Action del2 = SetObject; } 

Le délégué du convertisseur m’aide à visualiser les deux concepts en travaillant ensemble:

 delegate TOutput Converter(TInput input); 

TOutput représente la covariance où une méthode renvoie un type plus spécifique .

TInput représente la contravariance où une méthode est passée à un type moins spécifique .

 public class Dog { public ssortingng Name { get; set; } } public class Poodle : Dog { public void DoBackflip(){ System.Console.WriteLine("2nd smartest breed - woof!"); } } public static Poodle ConvertDogToPoodle(Dog dog) { return new Poodle() { Name = dog.Name }; } List dogs = new List() { new Dog { Name = "Truffles" }, new Dog { Name = "Fuzzball" } }; List poodles = dogs.ConvertAll(new Converter(ConvertDogToPoodle)); poodles[0].DoBackflip(); 

La covariance et la contravariance offrent une certaine flexibilité dans le code.

 using System; // ... class Program { static void Main(ssortingng[] args) { Console.ForegroundColor = ConsoleColor.Cyan; ReturnPersonDelegate returnPersonDelegate = ReturnPersonMethod; Employee employee = (Employee)returnPersonDelegate(); Console.WriteLine(employee._whatHappened); EmployeeParameterDelegate employeeParameterDelegate = EmployeeParameterMethod; employeeParameterDelegate(new Employee()); Console.ForegroundColor = ConsoleColor.Gray; Console.Write("Press any key to quit . . . "); Console.ReadKey(true); } delegate Person ReturnPersonDelegate(); // Covariance allows derived class as its return value delegate void EmployeeParameterDelegate(Employee employee); // Contravariance allows base class as its parameters static Employee ReturnPersonMethod() { Employee employee = new Employee(); employee._whatHappened = employee + ": Covariance"; return employee; } static void EmployeeParameterMethod(Person person) { person._whatHappened = ": Contravariance"; Console.WriteLine(person + person._whatHappened); } } class Person { public ssortingng _whatHappened; } class Employee : Person { }