Domanda

Se ho queste due classi:

class A {}
class B : A {}

e creo un List<A> ma voglio aggiungervi un List<B> chiamando List<A>.AddRange(List<B>) ma il compilatore rifiuta:

Argument '1': cannot convert from 'System.Collections.Generic.List<A>'
to 'System.Collections.Generic.IEnumerable<B>

che capisco perfettamente perché IEnumerable<B> non eredita da IEnumerable<A>, il suo tipo generico ha l'ereditarietà.

La mia soluzione è enumerare tramite List<B> e aggiungere individualmente elementi perché List<A>.Add(A item) funzionerà con gli elementi B:

foreach(B item in listOfBItems)
{
    listOfAItems.Add(item);
}

Tuttavia, è piuttosto non espressivo perché quello che voglio è solo AddRange.

Potrei usare

List<B>.ConvertAll<A>(delegate(B item) {return (A)item;});

ma è inutilmente contorto ed è un termine improprio perché non sto convertendo, sto lanciando .

Domanda:Se dovessi scrivere la mia raccolta simile a List quale metodo aggiungerei ad essa per permettermi di copiare una raccolta di B in una raccolta di A come una riga simile a List<A>.AddRange(List<B >) E mantenere la massima sicurezza del tipo.(E per massimo intendo che l'argomento è sia una raccolta che un controllo dell'ereditarietà del tipo.)

È stato utile?

Soluzione

In effetti, i tipi generici non sono varianti al momento. In C # 4.0, IEnumerable <B & Gt; sarà convertibile in IEnumerable <A > se B è convertibile in A tramite una conversione di riferimento . Per alcuni dettagli sulla progettazione di questa funzione, vedere:

http://blogs.msdn.com/ ericlippert / archivio / tag / covarianza + e + controvarianza / default.aspx

Altri suggerimenti

Questo sfortunatamente non funziona perché i generici in .net non supportano (ancora) la covarianza.

Puoi comunque creare un metodo o una classe di supporto piccoli per superare questo problema.

Se si implementa la propria classe di elenco, è possibile aggiungere la covarianza utilizzando un parametro generico aggiuntivo:

class MyList<T> {
    void AddRange<U>(IEnumerable<U> items) where U: T {
        foreach (U item in items) {
            Add(item);
        }
    }
}

Non puoi semplicemente:

listOfAItems.AddRange(listOfBItems.Cast<A>());

Sono stato in grado di raggiungere questo obiettivo usando LINQ ...

listOfAItems.AddRange(listOfBItems.Cast<A>());

Nel caso in cui ti trovi in ​​una situazione in cui i tipi generici non sono varianti, il seguente metodo di estensione può semplificarti la vita:

public static void AddRange<TList,TOther>(this List<TList> list, IEnumerable<TOther> collection) where TOther: TList {
    foreach(TOther e in collection) {
        list.Add(e);
    }
}

Invece di dover derivare da List<T> o avendo questo metodo in qualche classe di utilità, utilizzarlo come metodo di estensione ne semplifica l'utilizzo.Puoi anche trarre profitto dall'inferenza, quindi questa chiamata precedentemente non valida diventerà valida senza alcuna modifica:

List<Animal> animals;
List<Dog> dogs;
animals.AddRange(dogs);

L'unica cosa che posso inventare è questa

public class MyList<T> : List<T>
{
    public void AddRange<Tother>(IEnumerable<Tother> col)
        where Tother: T
    {    
        foreach (Tother item in col)
        {
            this.Add(item);
        }
    }
}

Chiamarlo significa fare MyList < A > .AddRange < B > (MyList < B >). Ciò non riesce se l'argomento non è enumerabile o se l'ereditarietà del tipo non funziona, quindi soddisfa il requisito di sicurezza del tipo massimo della mia domanda.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top