Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does casting a value as IEnumerable<T> behave differently based on how I initialized the value?

Tags:

c#

casting

I have some code that takes a value (of type object) and tries to cast it to an IEnumerable of ints and uints like so:

var idList = value as IEnumerable<int>;
var uintList = value as IEnumerable<uint>;

When the value is initialized this way:

uint number = 1;
object value = new []{ number };

both idList and uintList have values, but calling idList.ToList() results in an ArrayTypeMismatchException. However, when the value is generated with new List<uint>{ number }, idList is null as expected.

Additionally, calling var idList = value as IEnumerable<int>; in the immediate window in VS 2015 returns null as I would expect, even when the value was generated with a collection initializer.

.Net fiddle reproducing the error here.

What's going on here?

like image 657
RSid Avatar asked May 31 '16 18:05

RSid


1 Answers

I think that's because of unusual differences between how C# and CLR treats conversions between int and uint, as described in this answer. First note that this code won't compile:

uint[] a1 = new[] { 1u };
var a2 = (int[])a1;

Because C# doesn't believe there exists a cast. However if you go this way:

uint[] a1 = new[] { 1u };
var a2 = (int[]) (object) a1;

Runtime will decide if this cast is valid or not, and it (CLR) thinks differently and allows casting from uint[] to int[] (and visa versa), as desribed in answer I linked.

But the same is not true for List<int> and List<uint> - they are not treated in a special way by CLR and as such cannot be cast between each other.

So in your case, uint[] can be cast to int[] and int[] implements IEnumerable<int>, so your idList is not null. This is not true for Lists - hence your problem.

As for why ToList fails in first case, that's because internally it does something like that:

 uint[] a1 = new[] { 1u };
 var a2 = (int[]) (object) a1;
 // ToList copies contents to new array
 int[] copy = new int[a2.Length];
 Array.Copy(a2, copy, a2.Length);

And Array.Copy checks directly if type of elements in one array are compatible with type of elements in another array.

like image 183
Evk Avatar answered Nov 02 '22 19:11

Evk