Con <out T>
è possibile trattare il riferimento all'interfaccia come uno verso l'alto nella gerarchia.
Con <in T>
, puoi trattare il riferimento dell'interfaccia come uno verso il basso nella ricerca.
Vorrei provare a spiegarlo in termini più inglesi.
Supponiamo che tu stia recuperando un elenco di animali dal tuo zoo e intendi elaborarli. Tutti gli animali (nel tuo zoo) hanno un nome e un ID univoco. Alcuni animali sono mammiferi, alcuni sono rettili, alcuni sono anfibi, altri sono pesci, ecc. Ma sono tutti animali.
Quindi, con il tuo elenco di animali (che contiene animali di diversi tipi), 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 un elenco di pesci, ma devi trattarli come animali, funziona? Intuitivamente, dovrebbe funzionare, ma in C # 3.0 e prima, questo pezzo di codice non verrà compilato:
IEnumerable<Animal> animals = GetFishes();
Il motivo è che il compilatore non "sa" cosa intendi o puoi fare con la raccolta di animali dopo che l'hai recuperata. Per quanto ne sa, potrebbe esserci un modo IEnumerable<T>
per rimettere un oggetto nell'elenco, e questo potenzialmente consentirebbe di inserire un animale che non è un pesce, in una raccolta 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 semplicemente di compilare il codice. Questa è la covarianza.
Diamo un'occhiata alla controvarianza.
Dal momento che il nostro zoo può gestire tutti gli animali, può sicuramente gestire i pesci, quindi proviamo ad aggiungere alcuni pesci al nostro zoo.
In C # 3.0 e versioni precedenti, questo non viene compilato:
List<Fish> fishes = GetAccessToFishes();
fishes.Add(new Fish("Guppy"));
Qui, il compilatore potrebbe consentire questo pezzo di codice, anche se il metodo ritorna List<Animal>
semplicemente perché tutti i pesci sono animali, quindi se cambiassimo i tipi in questo:
List<Animal> fishes = GetAccessToFishes();
fishes.Add(new Fish("Guppy"));
Quindi funzionerebbe, ma il compilatore non può determinare che non stai tentando di farlo:
List<Fish> fishes = GetAccessToFishes();
Fish firstFist = fishes[0];
Poiché l'elenco è in realtà un elenco di animali, ciò non è consentito.
Quindi la controvarianza e la covarianza è il modo in cui tratti i riferimenti agli oggetti e cosa puoi farne.
Le parole chiave in
e out
in C # 4.0 contrassegnano specificamente l'interfaccia come l'una o l'altra. Con in
, puoi inserire il tipo generico (di solito T) nelle posizioni di input , che significa argomenti del metodo e proprietà di sola scrittura.
Con out
, è possibile inserire il tipo generico nelle posizioni di output , ovvero valori restituiti dal metodo, proprietà di sola lettura e parametri del metodo out.
Ciò ti consentirà di fare ciò che intendi fare con il codice:
IEnumerable<Animal> animals = GetFishes();
List<T>
ha entrambe le direzioni di entrata e di uscita su T, quindi non è né co-variante né contro-variante, ma un'interfaccia che ti ha permesso di aggiungere oggetti, come questo:
interface IWriteOnlyList<in T>
{
void Add(T value);
}
ti permetterebbe di fare questo:
IWriteOnlyList<Fish> fishes = GetWriteAccessToAnimals();
IWriteOnlyList<Animal>
fishes.Add(new Fish("Guppy")); <-- this is now safe
Ecco alcuni video che mostrano 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)
{
IBibbleOut<Base> b = GetOutDescendant();
IBibbleIn<Descendant> d = GetInBase();
}
static IBibbleOut<Descendant> GetOutDescendant()
{
return null;
}
static IBibbleIn<Base> GetInBase()
{
return null;
}
}
}
Senza questi segni, potrebbe essere compilato quanto segue:
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