2010-04-27 7 views
69

Le ho trovate in un libro di testo che sto leggendo su C#, ma ho difficoltà a capirle, probabilmente a causa della mancanza di contesto.Capire le interfacce covariant e controvarianti in C#

C'è una buona spiegazione concisa di cosa sono e di cosa sono utili là fuori?

Modifica di chiarimenti:

interfaccia covariante:

interface IBibble<out T> 
. 
. 

interfaccia controvarianti:

interface IBibble<in T> 
. 
. 
+0

Può essere utile: [Post di blog] (http://weblogs.asp.net/paulomorgado/archive/2010/04/13/c-4-0-covariance-and-contravariance-in-generics.aspx) – Krunal

+2

Questa è una breve e buona valutazione IMHO: http://blogs.msdn.com/csharpfaq/archive/2010/02/16/covariance-and-contravariance-faq.aspx – digEmAll

+0

Hmm è buono ma non lo è spiega _why_ che è ciò che mi sconcerta davvero. – NibblyPig

risposta

113

Con <out T>, si può trattare il riferimento di interfaccia come uno verso l'alto nella gerarchia.

Con <in T>, è possibile trattare il riferimento dell'interfaccia come uno verso il basso nella ricerca.

Lasciami provare a spiegarlo in termini più inglesi.

Diciamo che stai recuperando un elenco di animali dal tuo zoo e hai intenzione di elaborarli. Tutti gli animali (nel tuo zoo) hanno un nome e un ID univoco. Alcuni animali sono mammiferi, alcuni rettili, altri anfibi, altri pesci, ecc. Ma sono tutti animali.

Quindi, con la tua lista di animali (che contiene animali di diverso tipo), puoi dire che tutti gli animali hanno un nome, quindi ovviamente sarebbe sicuro ottenere il nome di tutti gli animali.

Tuttavia, cosa succede se si dispone solo di un elenco di pesci, ma è necessario trattarli come animali, funziona? Intuitivamente, dovrebbe funzionare, ma in C# 3.0 e prima, questo pezzo di codice non viene compilato:

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

La ragione di questo è che il compilatore non "sa" cosa si intende, o può , fai con la collezione di animali dopo averla recuperata. Per quanto ne sappia, potrebbe esserci un modo attraverso IEnumerable<T> per rimettere un oggetto nella lista, e questo ti permetterebbe potenzialmente di mettere un animale che non è un pesce, in una collezione che dovrebbe contenere solo pesci.

In altre parole, il compilatore non può garantire che questo non è consentito:

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

Così il compilatore solo rifiuta a titolo definitivo per compilare il codice. Questa è covarianza.

Diamo un'occhiata alla contravarianza.

Dato che il nostro zoo è in grado di gestire tutti gli animali, può certamente gestire il pesce, quindi proviamo ad aggiungere del pesce al nostro zoo.

In C# 3.0 e prima, questo non viene compilato:

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

Qui, il compilatore potrebbe permettere che questo pezzo di codice, anche se il metodo restituisce List<Animal> semplicemente perché tutti i pesci sono animali, quindi se abbiamo appena cambiato il tipo di questo:

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

Poi avrebbe funzionato, ma il compilatore non è in grado di determinare che non si sta cercando di fare questo:

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

Poiché l'elenco è in realtà un elenco di animali, questo non è consentito.

Così contromossa e co-varianza è il modo in cui si trattano i riferimenti agli oggetti e ciò che si è autorizzati a fare con essi.

Le parole chiave in e out in C# 4.0 indicano specificamente l'interfaccia come l'una o l'altra. Con in, è possibile inserire il tipo generico (in genere T) in input -positions, ovvero argomenti del metodo e proprietà di sola scrittura.

Con out, si è permesso di inserire il tipo generico in uscita posizioni a, che è i valori di ritorno del metodo, proprietà di sola lettura, e fuori parametri di metodo.

Questo vi permetterà di fare ciò che intendeva fare con il codice:

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> ha sia all'interno che fuori le indicazioni su T, quindi non è né co-variante né contra-variante, ma un'interfaccia che vi ha permesso di aggiungere oggetti, in questo modo:

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

permetterebbe di fare questo:

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

Ecco alcuni video che mostra i concetti:

Ecco un esempio:

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; 
     } 
    } 
} 

Senza questi segni, il seguente grado di compilare:

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

o questo:

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

Hmm, saresti in grado di spiegare l'obiettivo della covarianza e della contravarianza? Potrebbe aiutarmi a capirlo di più. – NibblyPig

+1

Vedere l'ultimo bit, che è quello che il compilatore ha impedito in precedenza, lo scopo di entrare e uscire è dire cosa si può fare con le interfacce (o tipi) che è sicuro, in modo che il compilatore non ti impedisca di fare al sicuro cose. –

+0

Risposta superba, ho visto i video che mi sono stati di grande aiuto e, combinato con il tuo esempio, l'ho risolto in questo momento. Rimane solo una domanda, ed è per questo che sono "out" e "in" obbligatori, perché lo studio visivo non sa automaticamente cosa si sta tentando di fare (o qual è il motivo dietro di esso)? – NibblyPig

4

This post è il migliore che ho letto sull'argomento

Insomma, covarianza/controvarianza/invarianza si occupa della trasmissione automatica dei tipi (dalla base alla derivata e viceversa).Questi tipi di cast sono possibili solo se vengono rispettate alcune garanzie in termini di azioni di lettura/scrittura eseguite sugli oggetti cast. Leggi il post per maggiori dettagli.

+0

ottimo articolo in effetti. :) grazie – gsimoes

+4

Link appare morto. Ecco una versione archiviata: https://web.archive.org/web/20140626123445/http://adamnathan.co.uk/?p=75 – si618