C# Variance Problem: Assigning List≪Derived≫ as List≪Base≫

C# variance problem: Assigning ListDerived as ListBase

Well this certainly won't be supported in C# 4. There's a fundamental problem:

List<Giraffe> giraffes = new List<Giraffe>();
giraffes.Add(new Giraffe());
List<Animal> animals = giraffes;
animals.Add(new Lion()); // Aargh!

Keep giraffes safe: just say no to unsafe variance.

The array version works because arrays do support reference type variance, with execution time checking. The point of generics is to provide compile-time type safety.

In C# 4 there will be support for safe generic variance, but only for interfaces and delegates. So you'll be able to do:

Func<string> stringFactory = () => "always return this string";
Func<object> objectFactory = stringFactory; // Safe, allowed in C# 4

Func<out T> is covariant in T because T is only used in an output position. Compare that with Action<in T> which is contravariant in T because T is only used in an input position there, making this safe:

Action<object> objectAction = x => Console.WriteLine(x.GetHashCode());
Action<string> stringAction = objectAction; // Safe, allowed in C# 4

IEnumerable<out T> is covariant as well, making this correct in C# 4, as pointed out by others:

IEnumerable<Animal> animals = new List<Giraffe>();
// Can't add a Lion to animals, as `IEnumerable<out T>` is a read-only interface.

In terms of working around this in your situation in C# 2, do you need to maintain one list, or would you be happy creating a new list? If that's acceptable, List<T>.ConvertAll is your friend.

C#, How to pass a List of a Derived class to a method that receives a List of the Base class?

The reason for such behaviour is explained here. In short - classes do not support variance in C# and List<AppleBox> is not List<FruitBox<Apple>>.

What you can do:

  • "convert" collection (actually create a new one):

with OfType<>().ToList()

AppleBox.ChooseFirst(appleBoxes.OfType<FruitBox<Apple>>().ToList())

or just ToList

AppleBox.ChooseFirst(appleBoxes.ToList<FruitBox<Apple>>())
  • change ChooseFirst signature to work with covariant IEnumerable<out T> interface:
public abstract class FruitBox<T>
{
public T item;

public static T ChooseFirst(IEnumerable<FruitBox<T>> fruitBoxes)
{
return fruitBoxes.First().item;
}
}

Contra/Covariance issue when assigning to Lazy

If there were an ILazy<T> interface, it could be declared as ILazy<out T>, and all would be well in your example: the T is only used in an output position, effectively.

However, Lazy<T> is a class. Covariance/contravariance can only be specified for delegates and interfaces, so Lazy<T> can't specify its covariance.

Therefore Lazy<Control> is incompatible with Lazy<T>, which is why the assignment isn't working. It's definitely frustrating, given that with the current API at least, it would be "safe" for it to be covariant, from a caller perspective.

(If you need this all over the place, you could declare your own ILazy<out T> interface and then write an implementation of that interface to wrap a Lazy<T>. I suspect that's more trouble than it's worth though.)

Why can't I assign a ListDerived to a ListBase?

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.

Cannot convert object of base instance to derived type (generics usage)

This issue has nothing to do with variance and generics. Next code will give you the same error:

class Interval1
{
public static Interval1 Empty = new Interval1();
}

class DateTimeInterval1 : Interval1
{
}

DateTimeInterval1 p = (DateTimeInterval1)DateTimeInterval1.Empty;

Reason being that Empty is instance of Interval not DateTimeInterval, so it can't be cast to DateTimeInterval.



Related Topics



Leave a reply



Submit