2010-10-27 18 views
18

È possibile trasmettere uno List<Subclass> a List<Superclass> in C# 4.0?covarianza in C#

Qualcosa in questo senso:

class joe : human {} 

List<joe> joes = GetJoes(); 

List<human> humanJoes = joes; 

Non è questo ciò di covarianza è per?

se si può fare:

human h = joe1 as human; 

perché non dovresti essere in grado di fare

List<human> humans = joes as List<human>; 

di quanto non sarebbe legale di farlo (Joe) gli esseri umani [0] perché questo l'oggetto è stato abbattuto .. e tutti sarebbero felici. Ora l'unica alternativa è creare un nuovo elenco

+2

Questo è fondamentalmente lo stesso di [In C#, perché non può un elenco oggetto essere memorizzato in una variabile di lista ] (http://stackoverflow.com/questions/6557/a-c-perché-sopraelevazione-a-ListString-oggetto-essere memorizzati-in-a-ListObject-variabile). –

+0

perché 'humans' si riferirebbe a un'istanza di' List ', che causerebbe problemi come illustrato nell'esempio di @ Jon. –

+0

sì, dopo aver corretto l'esempio l'ho ottenuto .. ha senso –

risposta

25

Non si può fare questo, perché non sarebbe sicuro. Considerate:

List<Joe> joes = GetJoes();  
List<Human> humanJoes = joes; 
humanJoes.Clear(); 
humanJoes.Add(new Fred()); 
Joe joe = joes[0]; 

Chiaramente l'ultima riga (se non una in meno un) ha a fallire - come Fred non è un Joe. L'invarianza di List<T> impedisce questo errore a compilare il tempo anziché il tempo di esecuzione.

+0

well humanJoes sia un elenco di Human, quindi non dovresti tentare (o riuscire) ad aggiungere un Joe da esso. ma ogni joe è umano, quindi dovrebbe essere sicuro lanciare tutti in sottoclasse in modo sicuro. –

+0

puoi sicuramente fare Human h = joesList [0] come Human .. ed è quello che stavo cercando/chiedendo ... accetta su un intero livello di lista –

+0

@Sonic: Perché non dovresti aggiungere un extra 'Joe' o "Fred" ad esso? * Quella * varietà di varianza è * sempre * accettabile. Provalo: 'Lista list = new List (); list.Add ("questa è una stringa)"; '. Perché ti aspetteresti che fallisca? –

2

No. Le funzioni di co/controvarianza di C# 4.0 supportano solo interfacce e delegati. Non supportano tipi di calcestruzzo come List<T>.

+0

http://stackoverflow.com/questions/245607/how-is-generic-covariance-contra-variance-implemented-in-c-4-0 –

+0

quindi avrei bisogno di scorrere e creare un nuovo oggetto per ogni joe come umano e aggiungerlo alla nuova lista? questa è l'unica opzione? –

+1

Non è possibile con 'IList ', perché è invariato. –

6

un'istanza di un nuovo essere umano-list che prende i Joes come input:

List<human> humanJoes = new List<human>(joes); 
1

No. Come ha detto Jared, le funzioni di co/controvarianza di C# 4.0 supportano solo interfacce e delegati. Tuttavia, non funziona con IList<T>, e il motivo è che IList<T> contiene metodi per aggiungere e modificare elementi nell'elenco, come dice la nuova risposta di Jon Skeet.

L'unico modo per essere in grado di lanciare una lista di "Joe" a "umana" è se l'interfaccia è puramente di sola lettura in base alla progettazione, qualcosa di simile:

public interface IListReader<out T> : IEnumerable<T> 
{ 
    T this[int index] { get; } 
    int Count { get; } 
} 

Anche un metodo Contains(T item) farebbe non essere consentito, poiché quando si trasmette IListReader<joe> a IListReader<human>, non esiste un metodo Contains(human item) in IListReader<joe>.

Si potrebbe "forzare" un cast da IList<joe> a IListReader<joe>, IListReader<human> o anche IList<human> utilizzando un GoInterface. Ma se la lista è abbastanza piccola da essere copiata, una soluzione più semplice è semplicemente copiarla in un nuovo List<human>, come ha fatto notare Paw.

0

Se permetto tuo List<Joe> joes per essere generalizzati come ...

List<Human> humans = joes; 

... i due riferimenti humans e joes sono, ora in poi, indicando la stessa lista. Il codice che segue l'assegnazione di cui sopra non ha modo di impedire l'inserimento/aggiunta di un'istanza di un altro tipo di umano, ad esempio un idraulico, nell'elenco.Dato che class Plumber: Human {}

humans.Add(new Plumber()); // Add() now accepts any Human not just a Joe 

lista che humans riferisce ad ora contiene sia joes e idraulici. Si noti che lo stesso oggetto elenco è ancora riferito al riferimento joes. Ora se utilizzo il riferimento joes per leggere dall'elenco oggetti, potrei far apparire un idraulico invece di un joe. Idraulico e Joe non sono noti per essere implicitamente interconvertibili ... quindi il mio recupero da un idraulico invece di un joe dalla lista scompone la sicurezza del tipo. Un idraulico non è certamente il benvenuto attraverso un riferimento a un elenco di joes.

Tuttavia nelle versioni recenti di C#, è possibile aggirare questa limitazione per una classe/raccolta generica implementando un'interfaccia generica il cui parametro di tipo ha un modificatore out. Diciamo che ora abbiamo ABag<T> : ICovariable<out T>. Il modificatore out limita solo le posizioni T a ouput (ad esempio i tipi di ritorno del metodo). Non puoi inserire alcuna T nella borsa. Puoi solo leggerli da questo. Ciò ci consente di generalizzare joes a ICovariable<Human> senza preoccuparci di inserire un Plumber in esso, poiché l'interfaccia non lo consente. Ora possiamo scrivere ...

ICovariable<Human> humans = joes ; // now its good ! 
humans.Add(new Plumber()); // error