Pregunta

Recientemente me encontré con problemas al intentar agregar Rango (IEnumerable) a una Lista. Probablemente un problema clásico, pero todavía no lo entiendo realmente.

Entiendo que los métodos que esperan un parámetro de Lista no están satisfechos con una Lista, porque podrían intentar agregar una Base a la Lista, lo que obviamente es imposible.

Pero si obtengo esto correctamente, ya que IEnumerables no se pueden cambiar, debería funcionar en este caso.

El código que pensé se ve así:

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
    }
}

Traté de pasar una pista al compilador en el método AddRange2, pero esto simplemente me llevó al problema.

¿Es defectuosa mi forma de pensar? ¿Es esto una limitación del lenguaje o es por diseño?

IIRC, el soporte para este tipo de operaciones se agregó a Java 1.5, por lo que quizás también se agregará a C # en algún momento en el futuro ...?

¿Fue útil?

Solución

Esto es covarianza y se solucionará en C # 4.0 / .NET 4.0. Por ahora, la opción genérica es la mejor respuesta (para IEnumerable < T > - no IList < T > etc ).

Pero dentro del método genérico, debe pensar en términos de T . También puede usar Cast < T > o OfType < T > con LINQ para lograr algo similar.

Otros consejos

En C # 3.0 puede usar el " Cast " método de extensión Si importa System.Linq y luego usa este código:

public void AddRange2<T>(IEnumerable<T> foos) where T : Foo
{
    m_Foos.AddRange (foos.Cast<Foo>());
}

Entonces debería funcionar para usted.

Hay una solución alternativa con el método de extensión:

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>() );

pero la " < Persona, Empleado > " es un poco incómodo: /.

La solución de conversión, por supuesto, podría generar excepciones de conversión de clase. La persona que publicó el trabajo de extensión enumerable alrededor dijo que era incómodo. Se me ocurrió una solución que es solo la mitad de incómoda, no sé si la usaré:

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);
    }
}

Uso:

IEnumerable mamiferos = UpTo < Mammal > .From (kennel.Dogs)

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top