9

I can implicitly cast an int to a IComparable. I can also cast a List or an array to a IEnumerable.

But why can't I implicitly cast a List to a IEnumerable?

I tested this with the .net framework 4.5 and Visual Studio 2012 Ultimate.

Code to test:

IComparable test1;
int t1 = 5;
test1 = t1; //OK

IEnumerable<int> test2;
List<int> t2 = new List<int>();
int[] t3 = new int[] { 5, 6 };
test2 = t2; //OK
test2 = t3; //OK

TabAlignment[] test;

IEnumerable<IComparable> test3;
test3 = t2; //error Cannot implicitly convert type 'System.Collections.Generic.List<int>' to 'System.Collections.Generic.IEnumerable<System.IComparable>'. An explicit conversion exists (are you missing a cast?)
user886079
  • 864
  • 2
  • 11
  • 18

2 Answers2

13

Generic variance doesn't apply to value types, basically. So while you can

You'd need to box each value:

IEnumerable<IComparable> test3 = t2.Cast<IComparable>();

So while this is valid because string is a reference type:

List<string> strings = new List<string>();
IEnumerable<IComparable> comparables = strings;

... the equivalent doesn't work for List<int>, and you need to box as you go.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • Having read through your answer and compared with mine (and then tested in code), I realise mine is wrong in this instance as it deals with conversions from List -> List rather than List -> IEnumerable, but I'm puzzled why that makes a difference. Can you explain? – Jon Egerton Feb 22 '13 at 17:11
  • @JonEgerton: I suspect you put generic type arguments in that comment, but I can't see them. Try again, with backticks around the code-related bits. – Jon Skeet Feb 22 '13 at 17:21
  • 1
    @JonEgerton: Okay, reading through your answer, then your comment again... - it's because `List` isn't covariant in `T`, but `IEnuemrable` is (as of .NET 4). Classes can never be variant. Search for "generic variance" on MSDN for more details :) – Jon Skeet Feb 22 '13 at 17:24
  • I think I get it, but I'll follow it up - thanks for the feedback. – Jon Egerton Feb 22 '13 at 17:26
2

It a common confusion with lists of generics, but basically if you generalise it it makes more sense:

Consider this setup:

public interface IA{
}

public class A : IA{
}

var listA = new List<A>();

The following line wouldn't work:

List<IA> listI = ListA;

Essentially this is because, even though A : IA, List<I> does not : List<A> - they're completely separate types.

You can do the cast easily enough though using the Cast method:

listI = ListA.Cast<IA>();

So in your case you could do

test3 = t2.Cast<IComparable>();
Jon Egerton
  • 40,401
  • 11
  • 97
  • 129