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 ...?

È stato utile?

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)

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