Pourquoi les concepts de «Covariance» et de «Contravariance» s’appliquent lors de la mise en œuvre des méthodes d’une interface?

Le cas d’utilisation est un peu ce qui ressemble à ceci:

public class SomeClass: IClonable { // Some Code //Implementing interface method Public object Clone() { //Some Clonning Code } } 

À présent, ma question est la suivante: “Pourquoi est-il impossible d’utiliser” SomeClass (dérivée de objec) “comme type de méthode de retour de la méthode Clone () si l’on considère les fondements de Covariance et Contravariance.

Quelqu’un peut-il m’expliquer la raison de cette implémentation de Microsoft ????

Une implémentation non interrompue de la variance d’implémentation d’interface doit être covariante dans le type de retour et contravariant dans les types d’argument .

Par exemple:

 public interface IFoo { object Flurp(Array array); } public class GoodFoo : IFoo { public int Flurp(Array array) { ... } } public class NiceFoo : IFoo { public object Flurp(IEnumerable enumerable) { ... } } 

Les deux sont légaux selon les “nouvelles” règles, non? Mais qu’en est-il de ceci:

 public class QuestionableFoo : IFoo { public double Flurp(Array array) { ... } public object Flurp(IEnumerable enumerable) { ... } } 

Difficile de dire quelle implémentation implicite est meilleure ici. Le premier correspond exactement au type d’argument, mais pas au type de retour. La seconde est une correspondance exacte pour le type de retour, mais pas le type d’argument. Je me penche vers le premier, car celui qui utilise l’interface IFoo ne peut lui donner qu’un Array , mais ce n’est pas encore tout à fait clair.

Et ce n’est pas le pire, de loin. Et si nous faisons cela à la place:

 public class EvilFoo : IFoo { public object Flurp(ICollection collection) { ... } public object Flurp(ICloneable cloneable) { ... } } 

Lequel gagne le prix? C’est une surcharge parfaitement valide, mais ICollection et ICloneable n’ont rien à voir avec l’autre et Array implémente. Je ne vois pas de solution évidente ici.

Cela ne fait qu’empirer si nous commençons à append des surcharges à l’interface elle-même:

 public interface ISuck { Stream Munge(ArrayList list); Stream Munge(Hashtable ht); ssortingng Munge(NameValueCollection nvc); object Munge(IEnumerable enumerable); } public class HateHateHate : ISuck { public FileStream Munge(ICollection collection); public NetworkStream Munge(IEnumerable enumerable); public MemoryStream Munge(Hashtable ht); public Stream Munge(ICloneable cloneable); public object Munge(object o); public Stream Munge(IDictionary dic); } 

Bonne chance pour essayer de résoudre ce mystère sans devenir fou.

Bien entendu, tout cela n’a plus sa raison d’être si vous affirmez que les implémentations d’interface doivent uniquement prendre en charge la variance de type retour et non la variance de type argument. Mais presque tout le monde considérerait qu’une telle demi-implémentation est complètement cassée et commencerait à envoyer des rapports de bogue par spam, alors je ne pense pas que l’équipe C # va le faire.

Je ne sais pas si c’est la raison officielle pour laquelle cela n’est pas pris en charge dans C # aujourd’hui, mais cela devrait servir d’exemple pour le type de code “en écriture seule” auquel il pourrait conduire, et qui fait partie de la conception de l’équipe C #. La philosophie est d’essayer d’empêcher les développeurs d’écrire du code épouvantable.

Laissez moi reformuler la question:

Les langages tels que C ++ permettent à une méthode de substitution d’avoir un type de retour plus spécifique que la méthode remplacée. Par exemple, si nous avons des types

 abstract class Enclosure {} class Aquarium : Enclosure {} abstract class Animal { public virtual Enclosure GetEnclosure(); } 

alors ce n’est pas légal en C # mais le code équivalent serait légal en C ++:

 class Fish : Animal { public override Aquarium GetEnclosure() { ... 

Comment appelle-t-on cette fonctionnalité de C ++?

La fonctionnalité est appelée “covariance de type de retour”. (Comme le souligne une autre réponse, il serait également possible de prendre en charge une “contravariance de type de paramètre formel”, contrairement à C ++.)

Pourquoi n’est-il pas supporté en C #?

Comme je l’ai souvent souligné, il n’est pas nécessaire d’indiquer la raison pour laquelle une fonctionnalité n’est pas prise en charge. l’état par défaut de toutes les fonctionnalités est “non pris en charge”. Ce n’est que lorsque beaucoup de temps et d’efforts sont consacrés à la mise en œuvre qu’une fonctionnalité est prise en charge. Au contraire, les fonctionnalités implémentées doivent avoir des raisons, et de bonnes raisons, vu le coût de leur création.

Cela dit, il y a deux gros “points contre” cette fonctionnalité qui sont les principales choses l’empêchant de se faire.

  1. Le CLR ne le supporte pas. Pour que cela fonctionne, il nous faut essentiellement implémenter la méthode de correspondance exacte, puis créer une méthode d’assistance qui l’appelle. C’est faisable mais ça devient compliqué.

  2. Anders pense que ce n’est pas une très bonne fonctionnalité linguistique. Anders est l’architecte en chef et s’il pense que c’est une mauvaise caractéristique, il y a de bonnes chances que cela ne se fasse pas. (Maintenant, remarquez, nous pensions que les parameters nommés et optionnels ne valaient pas le coût non plus, mais cela a finalement été fait. Parfois, il est clair que vous devez serrer les dents et implémenter une fonctionnalité que vous n’aimez pas vraiment. l’esthétique de afin de satisfaire une demande réelle.)

En bref, il y a certainement des moments où cela serait utile, et c’est une fonctionnalité fréquemment demandée. Cependant, il est peu probable que nous le fassions. L’avantage de la fonctionnalité ne paie pas ses coûts; cela complique considérablement l’parsing sémantique des méthodes et nous n’avons pas de moyen vraiment facile de la mettre en œuvre.

Vous devez implémenter les méthodes d’une interface exactement telles qu’elles sont dans l’interface. La méthode Clone d’ICloneable renvoie un object, votre SomeClass doit également renvoyer un object. Vous pouvez cependant renvoyer une instance SomeClass dans la méthode Clone de SomeClass sans aucun problème, mais la définition de la méthode doit correspondre à l’interface:

 public class SomeClass: IClonable { // Some Code //Implementing interface method Public object Clone() { SomeClass ret = new SomeClass(); // copy date from this instance to ret return ret; } } 

Pour ce qui est d’expliquer les raisons des décisions C #, Eric Lippert de Microsoft a beaucoup écrit sur Contra / CoVariance en C # … voici la liste des balises de son blog: http://blogs.msdn.com/ericlippert/archive/tags/ Covariance + et + Contravariance / default.aspx

[Modifier] En fonction de votre question, il pourrait s’agir du bon message .. http://blogs.msdn.com/ericlippert/archive/2007/10/26/covariance-and-contravariance-in-c-part-five- interface-variance.aspx

Cela ressemble au genre de choses pour lesquelles ils auraient pu utiliser des génériques, mais il semble y avoir une bonne raison pour laquelle ils ne l’ont pas fait.

On en parle ici:

http://bytes.com/topic/c-sharp/answers/469671-generic-icloneable

Fondamentalement, une interface générique qui permettrait: public class MyClass : IClonable

permettrait également: public class MyClass : IClonable

ce qui ne procure pas vraiment d’avantage et peut semer la confusion.

Selon la spécification C #, vous devez utiliser une méthode avec une signature identique lorsque vous substituez ou implémentez une méthode d’interface. Gardez à l’esprit que Microsoft ne possède pas C #. Leur compilateur C # est simplement leur implémentation. Alors, pourquoi la spécification ferait-elle les choses de cette façon? Je ne peux que deviner, mais je soupçonne que c’était pour la facilité de mise en œuvre.