87 votes

Comprendre les interfaces covariantes et contravariantes en C #

Je les ai trouvées dans un manuel que je lis en C #, mais j'ai du mal à les comprendre, probablement à cause du manque de contexte.

Existe-t-il une bonne explication concise de leur nature et de leur utilité?

Modifier pour clarification:

Interface covariante:

 interface IBibble<out T>
.
.
 

Interface contravariante:

 interface IBibble<in T>
.
.
 

144voto

Lasse V. Karlsen Points 148037

Avec <out T>, vous pouvez traiter la référence de l'interface de l'un vers le haut dans la hiérarchie.

Avec <in T>, vous pouvez traiter la référence de l'interface de l'un vers le bas dans le hiérarchique.

Laissez-moi vous expliquer en plus de termes anglais.

Disons que vous êtes à la recherche d'une liste d'animaux à partir de votre zoo, et vous avez l'intention de traiter. Tous les animaux (dans votre zoo) a un nom et un ID unique. Certains animaux sont des mammifères, certains sont des reptiles, certains sont des amphibiens, certains sont des poissons, etc. mais ils sont tous les animaux.

Ainsi donc, avec votre liste d'animaux (qui contient des animaux de différents types), on peut dire que tous les animaux ont un nom, donc, de toute évidence, il serait sûr d'obtenir le nom de tous les animaux.

Cependant, si vous avez une liste d'oiseaux seulement, mais besoin de les traiter comme des animaux, cela fonctionne? Intuitivement, cela devrait fonctionner, mais en C# 3.0 et avant, ce morceau de code ne compile pas:

IEnumerable<Animal> animals = GetFishes(); // returns IEnumerable<Fish>

La raison pour cela est que le compilateur ne sait pas ce que vous souhaitez, ou peut, avec les animaux de la collecte après que vous avez récupéré. Pour tout ce qu'il sait, il pourrait y avoir un chemin à travers IEnumerable<T> de mettre un objet à l'arrière dans la liste, et qui pourrait vous permettre de mettre un animal qui n'est pas un poisson, dans une collection qui est censé contenir que des poissons.

En d'autres termes, le compilateur ne peut pas garantir que ce n'est pas permis:

animals.Add(new Mammal("Zebra"));

Ainsi, le compilateur juste carrément refuse de compiler votre code. C'est la covariance.

Regardons la contravariance.

Depuis notre zoo peut s'occuper de tous les animaux, on peut certainement la manipulation des poissons, donc nous allons essayer d'ajouter un peu de poisson à notre zoo.

En C# 3.0 et avant, cela ne veut pas compiler:

List<Fish> fishes = GetAccessToFishes(); // for some reason, returns List<Animal>
fishes.Add(new Fish("Guppy"));

Ici, le compilateur pourrait permettre à ce morceau de code, même si la méthode renvoie List<Animal> tout simplement parce que tous les poissons sont des animaux, donc si nous avons juste changé les types de ceci:

List<Animal> fishes = GetAccessToFishes();
fishes.Add(new Fish("Guppy"));

Alors qu'il allait travailler, mais le compilateur ne peut pas déterminer que vous êtes, ne pas essayer de le faire:

List<Fish> fishes = GetAccessToFishes(); // for some reason, returns List<Animal>
Fish firstFist = fishes[0];

Étant donné que la liste est en fait une liste d'animaux, ce n'est pas autorisé.

Afin de contre - et de co-variance est la façon dont vous traitez les références de l'objet et ce que vous êtes autorisé à le faire avec eux.

L' in et out mots clés de C# 4.0 en particulier les marques de l'interface que l'un ou l'autre. Avec in, vous êtes autorisé à placer le type générique (généralement T) en entrée-postes, ce qui signifie des arguments de méthode, et de propriétés en écriture seule.

Avec out, vous êtes autorisé à placer le type générique en sortie-postes, ce qui revient valeurs de retour des méthodes, des propriétés en lecture seule, et les paramètres de la méthode.

Cela vous permettra de faire ce que l'intention de faire avec le code:

IEnumerable<Animal> animals = GetFishes(); // returns IEnumerable<Fish>
// since we can only get animals *out* of the collection, every fish is an animal
// so this is safe

List<T> a la fois dans et hors les directions sur T, de sorte qu'il n'est ni co-variante ni contre-variante, mais une interface vous permettant d'ajouter des objets, comme ceci:

interface IWriteOnlyList<in T>
{
    void Add(T value);
}

permettrait de faire ceci:

IWriteOnlyList<Fish> fishes = GetWriteAccessToAnimals(); // still returns
                                                            IWriteOnlyList<Animal>
fishes.Add(new Fish("Guppy")); <-- this is now safe

Voici quelques vidéos qui montre les concepts:

Voici un exemple:

namespace SO2719954
{
    class Base { }
    class Descendant : Base { }

    interface IBibbleOut<out T> { }
    interface IBibbleIn<in T> { }

    class Program
    {
        static void Main(string[] args)
        {
            // We can do this since every Descendant is also a Base
            // and there is no chance we can put Base objects into
            // the returned object, since T is "out"
            // We can not, however, put Base objects into b, since all
            // Base objects might not be Descendant.
            IBibbleOut<Base> b = GetOutDescendant();

            // We can do this since every Descendant is also a Base
            // and we can now put Descendant objects into Base
            // We can not, however, retrieve Descendant objects out
            // of d, since all Base objects might not be Descendant
            IBibbleIn<Descendant> d = GetInBase();
        }

        static IBibbleOut<Descendant> GetOutDescendant()
        {
            return null;
        }

        static IBibbleIn<Base> GetInBase()
        {
            return null;
        }
    }
}

Sans ces marques, les activités suivantes pourraient compiler:

public List<Descendant> GetDescendants() ...
List<Base> bases = GetDescendants();
bases.Add(new Base()); <-- uh-oh, we try to add a Base to a Descendant

ou ceci:

public List<Base> GetBases() ...
List<Descendant> descendants = GetBases(); <-- uh-oh, we try to treat all Bases
                                               as Descendants

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