2011-01-12 16 views
40

Qualcuno potrebbe fornirmi semplici esempi C# di convarianza, controvarianza, invarianza e contro-invarianza (se tale cosa esiste).Esempi semplici di co e controvarianza

Tutti gli esempi che ho visto finora sono stati solo il casting di alcuni oggetti in System.Object.

+0

qualcuno ha dato gettando un oggetto a 'System.Object' come un esempio di covarianza? Non è nemmeno giusto. –

+0

L'ho preso per indicare il passaggio di qualche tipo di oggetto (con un tipo specifico, più derivato) al posto di 'System.Object', non necessariamente il cast di' object' su 'System.Object', che sarebbe inutile. –

+0

_Variance_ non deve essere confuso con _casting_, non sono la stessa cosa. Vedi: [Differenza tra covarianza e upcasting] (http://stackoverflow.com/a/6707697/949681). – Pressacco

risposta

84

Qualcuno potrebbe fornirmi semplici esempi C# di convalescenza, controvarianza, invarianza e contro-invarianza (se tale cosa esiste).

Non ho idea di cosa significa "contro-invarianza". Il resto è facile.

Ecco un esempio di covarianza:

void FeedTheAnimals(IEnumerable<Animal> animals) 
{ 
    foreach(Animal animal in animals) 
     animal.Feed(); 
} 
... 
List<Giraffe> giraffes = ...; 
FeedTheAnimals(giraffes); 

L'interfaccia è IEnumerable<T>covariante. Il fatto che la giraffa è convertibile in animali implica che IEnumerable<Giraffe> è convertibile in IEnumerable<Animal>. Dal momento che List<Giraffe> implementa IEnumerable<Giraffe> questo codice riesce a C# 4; sarebbe fallito in C# 3 perché la covarianza su IEnumerable<T> non funzionava in C# 3.

Questo dovrebbe avere senso. Una sequenza di giraffe può essere trattata come una sequenza di animali.

Ecco un esempio di controvarianza:

void DoSomethingToAFrog(Action<Frog> action, Frog frog) 
{ 
    action(frog); 
} 
... 
Action<Animal> feed = animal=>{animal.Feed();} 
DoSomethingToAFrog(feed, new Frog()); 

Il Action<T> delegato è controvariante. Il fatto che la rana è convertibile in animali implica che Action<Animal> è convertibile in Action<Frog>. Si noti come questo rapporto è il direzione opposta della covariante uno; è per questo che è una variante "contro". A causa della convertibilità, questo codice ha successo; sarebbe fallito in C# 3.

Questo dovrebbe avere un senso. L'azione può prendere qualsiasi animale; abbiamo bisogno di un'azione che possa prendere qualsiasi Rana, e un'azione che può prendere qualsiasi Animale sicuramente può anche prendere qualsiasi Rana.

Un esempio di invarianza:

void ReadAndWrite(IList<Mammal> mammals) 
{ 
    Mammal mammal = mammals[0]; 
    mammals[0] = new Tiger(); 
} 

Possiamo passare un IList<Giraffe> a questa cosa? No, perché qualcuno sta per scrivere una Tigre e una Tigre non può essere in una lista di Giraffe. Possiamo passare un IList<Animal> in questa cosa? No, perché leggiamo un Mammifero e una lista di Animali potrebbe contenere una Rana. IList<T> è invariante. Può essere usato solo per quello che è in realtà.

Per alcune considerazioni aggiuntive sul design di questa funzione, vedere la mia serie di articoli su come l'abbiamo progettata e costruita.

http://blogs.msdn.com/b/ericlippert/archive/tags/covariance+and+contravariance/

+2

Potrebbe valere la pena di indicare nel tuo ultimo esempio, ovviamente, che * potresti * lanciare un 'Giraffe []' a un 'Mammifero []' e passarlo, il che comporterebbe un errore in fase di esecuzione. –

+3

@Dan: buon punto. C# supporta la covarianza dell'array "broken", in cui alcune conversioni covarianti sono consentite anche se possono causare arresti anomali in fase di runtime. –

+0

@Eric: Sì, ho sicuramente capito il ragionamento lì (credo): spesso gli sviluppatori vogliono un "sottoinsieme" dell'interfaccia di un 'T []' (o 'IList '), per accedere agli elementi per indice senza intenzione di scrivere alla collezione. Questo è il motivo per cui personalmente credo fermamente che ci dovrebbe essere qualcosa come un'interfaccia covariante 'IArray ' con un getter indicizzato nel BCL. Ciò consentirebbe a un 'Elenco ' di agire come un 'IList ' quando è usato solo per l'accesso indicizzato e non le sue caratteristiche mutabili (cioè, nello stesso caso in cui un 'Giraffe []' può tranquillamente agire come un 'Mammifero [] '). –

3

L'invarianza (in questo contesto) è l'assenza di entrambe le variazioni di co e contro. Quindi la contro-invarianza non ha senso. Qualsiasi parametro di tipo non etichettato come in o out è invariato. Ciò significa che questo parametro di tipo può essere sia consumato che restituito.

Un buon esempio di co-varianza è IEnumerable<out T> perché è possibile sostituire uno IEnumerable<Derived>IEnumerable<Base>. O Func<out T> che restituisce i valori di tipo T.
Ad esempio un IEnumerable<Dog> può essere convertito in IEnumerable<Animal> perché qualsiasi cane è un animale.

Per contro varianza è possibile utilizzare qualsiasi interfaccia o delegato. IComparer<in T> o Action<in T> mi vengono in mente. Questi non restituiscono mai una variabile di tipo T, solo la ricevono. E se non si riceve uno Base, è possibile passare in un Derived.

Pensandoli come parametri di tipo solo input o output-only, è più facile capire l'IMO.

E la parola Invarianti in genere non viene utilizzata insieme alla varianza di tipo, ma con invarianti di classe o metodo e rappresenta una proprietà conservata. Vedi this stackover thread dove vengono discusse le differenze tra invarianti e invarianza.

2

Se si considera l'uso regolare di generici, si utilizza regolarmente un'interfaccia per gestire un oggetto, ma l'oggetto è un'istanza di una classe - non è possibile creare un'istanza dell'interfaccia. Usa un semplice elenco di stringhe come esempio.

IList<string> strlist = new List<string>(); 

Sono sicuro che siete consapevoli dei vantaggi di utilizzare un IList<> piuttosto che direttamente con un List<>. Permette l'inversione del controllo e potresti decidere di non voler più usare uno List<>, ma preferisci il LinkedList<>. Il codice sopra funziona bene perché il tipo generico di interfaccia e classe è lo stesso: string.

Può essere un po 'più complicato se si desidera creare un elenco di stringhe. Considerate questo esempio:

IList<IList<string>> strlists = new List<List<string>>(); 

Questo chiaramente non compilerà, perché i tipi generici argomenti IList<string> e List<string> non sono gli stessi. Anche se hai dichiarato la lista esterna come una classe regolare, come List<IList<string>>, non si compilerebbe - gli argomenti tipo non corrispondono.

Quindi ecco dove la covarianza può essere d'aiuto. Covariance consente di utilizzare un tipo derivato in più come argomento di tipo in questa espressione. Se IList<> è stato creato per essere covariante, simplpy compila e risolve il problema.Purtroppo, non è IList<> covariante, ma uno le interfacce che estende è:

IEnumerable<IList<string>> strlists = new List<List<string>>(); 

Questo codice compila ora, gli argomenti di tipo sono gli stessi erano sopra.

Problemi correlati