C # Impossibile convertire da IEnumerable < Base > a IEnumerable < Derivato >
-
10-07-2019 - |
Domanda
Di recente mi sono imbattuto in problemi quando provavo ad aggiungere (IEnumerable) a un elenco. Probabilmente un problema classico, ma non l'ho ancora capito.
Comprendo che i metodi che prevedono un parametro List non sono soddisfatti di un Elenco, perché potrebbero provare ad aggiungere una Base all'elenco, il che è ovviamente impossibile.
Ma se lo capisco correttamente, dato che IEnumerables non può essere modificato, dovrebbe funzionare in questo caso.
Il codice a cui ho pensato è simile al seguente:
class Foo
{
}
class Bar : Foo
{
}
class FooCol
{
private List<Foo> m_Foos = new List<Foo> ();
public void AddRange1(IEnumerable<Foo> foos)
{
m_Foos.AddRange (foos); // does work
}
public void AddRange2<T>(IEnumerable<T> foos) where T : Foo
{
m_Foos.AddRange (foos); // does not work
}
}
class Program
{
static void Main(string[] args)
{
FooCol fooCol = new FooCol ();
List<Foo> foos = new List<Foo> ();
List<Bar> bars = new List<Bar> ();
fooCol.AddRange1 (foos); // does work
fooCol.AddRange1 (bars); // does not work
fooCol.AddRange2 (foos); // does work
fooCol.AddRange2 (bars); // does work
}
}
Ho provato a trasmettere un suggerimento al compilatore nel metodo AddRange2, ma questo è appena passato al problema.
Il mio modo di pensare è difettoso? È una limitazione del linguaggio o è di progettazione?
IIRC, il supporto per questo tipo di operazioni è stato aggiunto a Java 1.5, quindi forse verrà aggiunto a C # ad un certo punto anche in futuro ...?
Soluzione
Questa è covarianza e verrà risolta in C # 4.0 / .NET 4.0. Per ora, l'opzione generica è la risposta migliore (per IEnumerable < T >
- non IList < T >
etc ).
Ma all'interno del metodo generico, devi pensare in termini di T
. Puoi anche utilizzare Cast < T >
o OfType < T >
con LINQ per ottenere qualcosa di simile.
Altri suggerimenti
In C # 3.0 puoi utilizzare il " Cast " metodo di estensione. Se importi System.Linq e quindi usi questo codice:
public void AddRange2<T>(IEnumerable<T> foos) where T : Foo
{
m_Foos.AddRange (foos.Cast<Foo>());
}
Quindi dovrebbe funzionare per te.
Esiste una soluzione alternativa con il metodo di estensione:
public static IEnumerable<TBase> ToBaseEnumerable<TBase, TDerived>( this IEnumerable<TDerived> items ) where TDerived : TBase {
foreach( var item in items ) {
yield return item;
}
}
...
IEnumerable<Employee> employees = GetEmployees(); //Emplyoee derives from Person
DoSomethingWithPersons( employees.ToBaseEnumerable<Person, Employee>() );
ma la persona " < Persona, dipendente > " è un po 'imbarazzante: /.
La soluzione cast ovviamente potrebbe generare eccezioni al cast di classe. La persona che ha pubblicato il lavoro di estensione enumerable ha detto che era imbarazzante. Ho trovato una soluzione che è solo la metà imbarazzante, non so se la userò:
public static class UpTo<T>
{
public static IEnumerable<T> From<F>(IEnumerable<F> source) where F:T
{
// this cast is guaranteed to work
return source.Select(f => (T) f);
}
}
Utilizzo:
IEnumerable mammals = UpTo < Mammal > .From (kennel.Dogs)