70

Look at the following example (partially taken from MSDN Blog):

class Animal { }
class Giraffe : Animal { }

static void Main(string[] args)
{
    // Array assignment works, but...
    Animal[] animals = new Giraffe[10]; 

    // implicit...
    List<Animal> animalsList = new List<Giraffe>();

    // ...and explicit casting fails
    List<Animal> animalsList2 = (List<Animal>) new List<Giraffe>();
}

Is this a covariance problem? Will this be supported in the future C# release and are there any clever workarounds (using only .NET 2.0)?

AndiDog
  • 68,631
  • 21
  • 159
  • 205

4 Answers4

125

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.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • +1 just to add to Jon's answer (not that he needs any help ), following the `Func` to `Func` example and the fact that `T` is contravariant in `Action`. The following `Action = Action` would not work in C# 4. – Stan R. Jan 09 '10 at 16:17
  • Thanks for also pointing out the type safety problem. That's really helpful. – AndiDog Jan 09 '10 at 16:25
  • 5
    What is wrong with `animals.Add(new Lion()); // Aargh!`? If you can cast a `Lion` and use it as an `Animal`, and if you are using all of the elements in `animals` as `Animal`s, then what is the problem? – Jeff Apr 12 '17 at 20:04
  • 7
    @Jeff: Because `animals` *actually* refers to a `List`. So you'd end up with a `Lion` reference in a `List`, which is bad... – Jon Skeet Apr 12 '17 at 20:32
  • 3
    Worth mentioning that a better fit for List might be IReadOnlyList which is also safely covariant, being immutable. – x0n Oct 18 '18 at 15:01
  • 1
    @x0n: I'm happy for you to mention that, but I'm not going to update all my old answer with types introduced later on. – Jon Skeet Oct 18 '18 at 15:08
  • @JonSkeet I realise that :) That's why it wasn't put forward as a correction. I realized that when I saw the date of your original answer :) – x0n Oct 18 '18 at 15:18
  • @JonSkeet Having a `Lion` reference in a `List` *casted* as a `List` does not seem bad to me (logically speaking. Could be technically problematic for the .NET runtime though) – Alexandre Daubricourt Feb 05 '21 at 12:07
  • 2
    @AlexandreDaubricourt: But some other piece of code could refer to the same object as a `List` (as in my example). So you'd get an error when that tried to do anything, but that could be much, much later. Better to spot the problem early, IMO. – Jon Skeet Feb 05 '21 at 12:09
17

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.

Lee
  • 142,018
  • 20
  • 234
  • 287
9

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.

EMP
  • 59,148
  • 53
  • 164
  • 220
Noldorin
  • 144,213
  • 56
  • 264
  • 302
5

Covariance/contravariance can't be supported on mutable collections as others have mentioned because it's impossible to guarantee type safety both ways at compile time; however, it is possible to do a quick one-way conversion in C# 3.5, if that is what you're looking for:

List<Giraffe> giraffes = new List<Giraffe>();
List<Animal> animals = giraffes.Cast<Animal>().ToList();

Of course it's not the same thing, it's not actually covariance - you're actually creating another list, but it is a "workaround" so to speak.

In .NET 2.0, you can take advantage of array covariance to simplify the code:

List<Giraffe> giraffes = new List<Giraffe>();
List<Animal> animals = new List<Animal>(giraffes.ToArray());

But be aware that you're actually creating two new collections here.

Aaronaught
  • 120,909
  • 25
  • 266
  • 342
  • I think these are quite good workarounds for my simple application. At least they're good for readability - not for performance. – AndiDog Jan 09 '10 at 16:26
  • @JohnAskew: I'm not sure what your point is here - there's no `Cast` method on `IList`, it's the `Enumerable.Cast` extension method, which takes an `IEnumerable` and explicitly casts every element to `T2` and then returns it (as an `IEnumerable`, not an `IList`). It has nothing to do with `IList` at all, save for the fact that `IList` happens to inherit from `IEnumerable` and thus supports the `Enumerable` extension methods. There's no covariance whatsoever there. – Aaronaught Jul 22 '14 at 14:17
  • I didn't see the reference to C# 3.5 and was referring to C# 4.0 where the IEnumerable interface was updated to be IEnumerable and the Cast method changed to use covariance: IEnumerable typedSource = source as IEnumerable rather than an iteration with explicit casting. – John Askew Jul 23 '14 at 14:22