¿Por qué no puedo asignar una Lista a una Lista ?

Definí la siguiente clase:

public abstract class AbstractPackageCall { ... } 

También defino una subclase de esta clase:

 class PackageCall : AbstractPackageCall { ... } 

También hay varias otras subclases de AbstractPackageCall

Ahora quiero hacer la siguiente llamada:

  List calls = package.getCalls(); 

Pero siempre obtengo esta excepción:

 Error 13 Cannot implicitly convert type 'System.Collections.Generic.List' to 'System.Collections.Generic.List' 

¿Cuál es el problema aquí? Este es el método Package # getCalls

  internal List getCalls() { return calls; } 

    La forma más simple de entender por qué esto no está permitido es el siguiente ejemplo:

     abstract class Fruit { } class Apple : Fruit { } class Banana : Fruit { } // This should intuitively compile right? Cause an Apple is Fruit. List fruits = new List(); // But what if I do this? Adding a Banana to a list of Apples fruits.Add(new Banana()); 

    La última statement arruinaría el tipo de seguridad de .NET.

    Sin embargo, las matrices permiten esto:

     Fruit[] fruits = new Apple[10]; // This is perfectly fine 

    Sin embargo, poner un Banana en las fruits todavía rompería la seguridad del tipo, así que .NET tiene que hacer un control de tipo en cada inserción del arreglo y lanzar una excepción si no es realmente un Apple . Esto es potencialmente un golpe de rendimiento (pequeño), pero esto se puede eludir mediante la creación de un envoltorio de struct alrededor de cualquier tipo ya que esta comprobación no ocurre para los tipos de valor (porque no pueden heredar de nada). Al principio, no entendí por qué se tomó esta decisión, pero se encontrará con frecuencia por qué esto puede ser útil. El más común es String.Format , que toma params object[] y cualquier matriz se puede pasar a esto.

    Sin embargo, en .NET 4, hay una covarianza / contravarianza segura, que te permite realizar asignaciones como estas, pero solo si son probadamente seguras. ¿Qué es probadamente seguro?

     IEnumerable fruits = new List(); 

    Lo anterior funciona en .NET 4, porque IEnumerable convirtió en IEnumerable . La out significa que T solo puede salir de las fruits y que no existe ningún método en IEnumerable que tome T como parámetro, por lo que nunca se puede pasar incorrectamente un Banana a IEnumerable .

    La contradicción es muy parecida, pero siempre me olvido de los detalles exactos. Como era de esperar, ahora existe la palabra clave in en los parámetros de tipo.

    Ahora si quieres hacer la siguiente llamada:

     List calls = package.getCalls(); // select only AbstractPackageCall items List calls = calls.Select(); calls.Add(new AnotherPackageCall()); 

    Yo llamo a esto generalización.

    Además, puedes usar esto más específico:

     List calls = package.getCalls(); // select only AnotherPackageCall items List calls = calls.Select(); calls.Add(new AnotherPackageCall()); 

    Implemente esto usando el método de extensión:

     public static class AbstractPackageCallHelper { public static List Select(this List source) where T : AbstractPackageCall where U : T { List target = new List(); foreach(var element in source) { if (element is U) { target.Add((U)element); } } return target; } } 

    Por qué lo que intentas no funciona

    Le está pidiendo al comstackdor que trate una List como una List , que no es así. Otra cosa es que cada una de esas instancias de PackageCall sea ​​de hecho una AbstractPackageCall .

    ¿Qué funcionaría en su lugar?

     var calls = package.getCalls().Cast().ToList(); 

    Por qué lo que intentas nunca se le permitirá trabajar

    Aunque cada elemento dentro del valor de retorno de package.getCalls() proviene de AbstractPackageCall , no podemos tratar la lista completa como List . Esto es lo que sucedería si pudiéramos:

     var calls = package.getCalls(); // calls is List List apcs = calls; // ILLEGAL, but assume we could do it apcs.Add(SomeOtherConcretePackageCall()); // BOOM! 

    Si pudiéramos hacer eso, entonces podríamos agregar un SomeOtherConcretePackageCall a una List .

    Porque List no hereda de List . Puede usar el método de extensión Cast<>() , como:

     var calls = package.getCalls().Cast(); 

    No puede realizar esta conversión, porque una List puede contener cualquier cosa que se derive de AbstractPackageCall , mientras que una List no puede; solo puede contener elementos que se derivan de PackageCall .

    Considere si este elenco fue permitido:

     class AnotherPackageCall : AbstractPackageCall { } // ... List calls = package.getCalls(); calls.Add(new AnotherPackageCall()); 

    Acaba de agregar un AnotherPackageCall en una List – ¡pero AnotherPackageCall no se deriva de PackageCall ! Es por eso que esta conversión no está permitida.