c# - trigonometricas - derivada de una funcion real
¿Por qué no puedo asignar una Lista<Derivada> a una Lista<Base>? (5)
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<AbstractPackageCall> calls = package.getCalls();
Pero siempre obtengo esta excepción:
Error 13 Cannot implicitly convert type ''System.Collections.Generic.List<Prototype_Concept_2.model.PackageCall>'' to ''System.Collections.Generic.List<Prototype_Concept_2.model.AbstractPackageCall>''
¿Cuál es el problema aquí? Este es el método Package # getCalls
internal List<PackageCall> getCalls()
{
return calls;
}
Por qué lo que intentas no funciona
Le está pidiendo al compilador que trate una List<PackageCall>
como una List<AbstractPackageCall>
, 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<AbstractPackageCall>().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<AbstractPackageCall>
. Esto es lo que sucedería si pudiéramos:
var calls = package.getCalls(); // calls is List<PackageCall>
List<AbstractPackageCalls> 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<PackageCall>
.
Ahora si quieres hacer la siguiente llamada:
List<PackageCall> calls = package.getCalls();
// select only AbstractPackageCall items
List<AbstractPackageCall> calls = calls.Select();
calls.Add(new AnotherPackageCall());
Yo llamo a esto generalización.
Además, puedes usar esto más específico:
List<PackageCall> calls = package.getCalls();
// select only AnotherPackageCall items
List<AnotherPackageCall> calls = calls.Select();
calls.Add(new AnotherPackageCall());
Implemente esto usando el método de extensión:
public static class AbstractPackageCallHelper
{
public static List<U> Select(this List<T> source)
where T : AbstractPackageCall
where U : T
{
List<U> target = new List<U>();
foreach(var element in source)
{
if (element is U)
{
target.Add((U)element);
}
}
return target;
}
}
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<Fruit> fruits = new List<Apple>();
// But what if I do this? Adding a Banana to a list of Apples
fruits.Add(new Banana());
La última declaración 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<Fruit> fruits = new List<Apple>();
Lo anterior funciona en .NET 4, porque IEnumerable<T>
convirtió en IEnumerable<out T>
. La out
significa que T
solo puede salir de las fruits
y que no existe ningún método en IEnumerable<out T>
que tome T
como parámetro, por lo que nunca se puede pasar incorrectamente un Banana
a IEnumerable<Fruit>
.
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.
No puede realizar esta conversión, porque una List<AbstractPackageCall>
puede contener cualquier cosa que se derive de AbstractPackageCall
, mientras que una List<PackageCall>
no puede; solo puede contener elementos que se derivan de PackageCall
.
Considere si este elenco fue permitido:
class AnotherPackageCall : AbstractPackageCall { }
// ...
List<AbstractPackageCall> calls = package.getCalls();
calls.Add(new AnotherPackageCall());
Acaba de agregar un AnotherPackageCall
en una List<PackageCall>
- ¡pero AnotherPackageCall
no se deriva de PackageCall
! Es por eso que esta conversión no está permitida.
Porque List<PackageCall>
no hereda de List<AbstractPackageCall>
. Puede usar el método de extensión Cast<>()
, como:
var calls = package.getCalls().Cast<AbstractPackageCall>();