6

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?

RSid
  • 768
  • 1
  • 8
  • 30
  • You might look at this stack overflow post: http://stackoverflow.com/questions/7173266/casting-array-to-ienumerablet – DHP May 31 '16 at 18:31
  • 2
    If you I initialize `value` as you have then the line `var idList = value as IEnumerable;` gives me a compile-time error, not an runtime exception – Matt Burland May 31 '16 at 18:34
  • Hm, no compile errors here, but I realized that in simplifying the code path I left out that value has been cast to an object type before it gets cast back to an IEnumerable. Will add that to the question accordingly, thanks. – RSid May 31 '16 at 18:44
  • I think part of the problem is that you're trying to do an implicit conversion from an `IEnumerable` to an `IEnumerable`. Because these are IEnumerables, I think they're being evaluated lazily. When you call `ToList` your forcing an evaluation of the object. Note also that there's no implicit conversion from a uint to an int. uint.MaxValue is twice int.MaxValue. uint.MinValue is of course zero. – user2023861 May 31 '16 at 19:34

1 Answers1

3

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.

Community
  • 1
  • 1
Evk
  • 98,527
  • 8
  • 141
  • 191
  • That's really weird, thanks for explaining! I wouldn't expect the CLR and language to disagree either, but :shrug: – RSid Jun 01 '16 at 18:20