Why can't I assign a List<Derived> to a List<Base>?
I defined the following class:
public abstract class AbstractPackageCall
{
...
}
I also define a subclass of this class:
class PackageCall : AbstractPackageCall
{
...
}
There are also several other subclases of AbstractPackageCall
Now I want to make the following call:
List<AbstractPackageCall> calls = package.getCalls();
But I always get this exception:
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>'
What is the problem here? This is the method Package#getCalls
internal List<PackageCall> getCalls()
{
return calls;
}
Solution 1:
The simplest way to understand why this is not allowed is the following example:
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());
The last statement would ruin the type safety of .NET.
Arrays however, do allow this:
Fruit[] fruits = new Apple[10]; // This is perfectly fine
However, putting a Banana
into fruits
would still break type safety, so therefor .NET has to do a type check on every array insertion and throw an exception if it's not actually an Apple
. This is potentially a (small) performance hit, but this can be circumvented by creating a struct
wrapper around either type as this check does not happen for value types (because they can't inherit from anything). At first, I didn't understand why this decision was made, but you'll encounter quite often why this can be useful. Most common is String.Format
, which takes params object[]
and any array can be passed into this.
In .NET 4 though, there's type safe covariance/contravariance, which allows you to make some assignments like these, but only if they're provably safe. What's provably safe?
IEnumerable<Fruit> fruits = new List<Apple>();
The above works in .NET 4, because IEnumerable<T>
became IEnumerable<out T>
. The out
means that T
can only ever come out of fruits
and that there's no method at all on IEnumerable<out T>
that ever takes T
as a parameter, so you can never incorrectly pass a Banana
into IEnumerable<Fruit>
.
Contravariance is much the same but I always forget the exact details on it. Unsurprisingly, for that there's now the in
keyword on type parameters.
Solution 2:
Now if you want to make the following call:
List<PackageCall> calls = package.getCalls();
// select only AbstractPackageCall items
List<AbstractPackageCall> calls = calls.Select();
calls.Add(new AnotherPackageCall());
I call this generalization.
Also, you can use this more specific:
List<PackageCall> calls = package.getCalls();
// select only AnotherPackageCall items
List<AnotherPackageCall> calls = calls.Select();
calls.Add(new AnotherPackageCall());
Implement this using extension method:
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;
}
}