C# variance problem: Assigning List<Derived> as List<Base>
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.
It will work in C#4 for IEnumerable<T>
, so you can do:
IEnumerable<Animal> animals = new List<Giraffe>();
However List<T>
is not a covarient projection, so you cannot assign lists as you have done above since you could do this:
List<Animal> animals = new List<Giraffe>();
animals.Add(new Monkey());
Which is clearly not valid.
In terms of List<T>
, I'm afraid you're out of luck. However, .NET 4.0/C# 4.0 adds support for covariant/contravariant interfaces. Specifically, IEnumerable<T>
is now defined as IEnumerable<out T>
, which means that the type parameter is now covariant.
This means you can do something like this in C# 4.0...
// implicit casting
IEnumerable<Animal> animalsList = new List<Giraffe>();
// explicit casting
IEnumerable<Animal> animalsList2 = (IEnumerable<Animal>) new List<Giraffe>();
Note: Array types have also been covariant (at least since .NET 1.1).
I think it's a shame that variance support wasn't added for IList<T>
and other similar generic interfaces (or generic classes even), but oh well, at least we have something.