Comprensione delle interfacce covariant e controvarianti in C #

Ho trovato questi in un libro di testo che sto leggendo su C #, ma ho difficoltà a comprenderli, probabilmente a causa della mancanza di contesto.

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

Modifica per chiarimenti:

Interfaccia Covariant:

interface IBibble . . 

Interfaccia controvariante:

 interface IBibble . . 

Con , puoi trattare il riferimento all’interfaccia come uno verso l’alto nella gerarchia.

Con , puoi trattare il riferimento all’interfaccia come uno verso il basso nella ricerca.

Lasciatemi provare a spiegarlo in termini più inglesi.

Diciamo che stai recuperando una lista 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 hai solo una lista di pesci, ma devi trattarli come animali, funziona? Intuitivamente, dovrebbe funzionare, ma in C # 3.0 e prima, questa parte di codice non verrà compilata:

 IEnumerable animals = GetFishes(); // returns IEnumerable 

La ragione di ciò è che il compilatore non “sa” cosa intendi, o puoi fare, con la collezione di animali dopo averla recuperata. Per quanto ne sappia, potrebbe esserci un modo attraverso IEnumerable per rimettere un object nella lista, e questo ti permetterebbe potenzialmente di mettere un animale che non è un pesce, in una collezione che dovrebbe contenere solo pesce.

In altre parole, il compilatore non può garantire che ciò non sia consentito:

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

Quindi il compilatore si rifiuta di compilare il tuo 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 precedenti, questo non viene compilato:

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

Qui, il compilatore potrebbe consentire questo pezzo di codice, anche se il metodo restituisce List semplicemente perché tutti i pesci sono animali, quindi se abbiamo solo cambiato i tipi in questo modo:

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

Quindi funzionerebbe, ma il compilatore non può determinare che non stai provando a farlo:

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

Poiché la lista è in realtà un elenco di animali, questo non è permesso.

Quindi contra- e co-varianza è il modo in cui tratti i riferimenti agli oggetti e ciò che ti è permesso fare con loro.

Le parole chiave in e out in C # 4.0 segnano specificamente l’interfaccia come l’una o l’altra. Con in , puoi inserire il tipo generico (di solito T) in input -positions, ovvero argomenti di metodo e proprietà di sola scrittura.

Con out , puoi inserire il tipo generico in output -positions, che è valori di ritorno del metodo, proprietà di sola lettura e parametri del metodo out.

Questo ti permetterà di fare ciò che si intende fare con il codice:

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

List ha sia in e out-direction su T, quindi non è né co-variant né contro-variant, ma un’interfaccia che ti permette di aggiungere oggetti, come questo:

 interface IWriteOnlyList { void Add(T value); } 

ti permetterebbe di fare questo:

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

Ecco alcuni video che mostrano i concetti:

  • Covarianza e controvarianza - VS2010 C # Parte 1 di 3
  • Covarianza e controvarianza - VS2010 C # Parte 2 di 3
  • Covarianza e controvarianza - VS2010 C # Parte 3 di 3

Ecco un esempio:

 namespace SO2719954 { class Base { } class Descendant : Base { } interface IBibbleOut { } interface IBibbleIn { } 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 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 d = GetInBase(); } static IBibbleOut GetOutDescendant() { return null; } static IBibbleIn GetInBase() { return null; } } } 

Senza questi segni, potrebbe essere compilato quanto segue:

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

o questo:

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

Questo post è il migliore che ho letto sull’argomento

In breve, covarianza / controvarianza / invarianza riguarda il casting automatico dei tipi (da base a 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.