var uints = GetArray(); if (uints is int[]) { var list = ((int[])uints).ToList(); // fails when call ToList() } private Array GetArray() { var result = new uint[] { uint.MaxValue,2,3,4,5 }; return result; }
然后我在Why does “int[] is uint[] == true” in C#中诉诸Jon的答案,它告诉我,由于GetArray()返回一个Array,转换在运行时被推迟,CLR允许这种类型的int [](反之亦然)转换.如果我在转换后检查值,它实际上可以正常工作:
foreach (var i in ((int[])units)) { System.Console.WriteLine(i.GetType()); System.Console.WriteLine(i); }
我会得到:
System.Int32 -1 System.Int32 2 //skipped...
但是我有点困惑为什么在调用ToList()时会失败,因为下面的代码可以正常工作吗?
internal class Animal { } internal class Cat : Animal { } var cats = new Cat[] { new Cat(),new Cat() }; List<Animal> animals = ((Animal[])cats).ToList(); //no exception
foreach (var i in ((int[])units)) { System.Console.WriteLine(i.GetType()); System.Console.WriteLine(i); }
不能信任,BTW foreach也可以执行对元素类型的转换,最好是尝试通用的Array.GetValue方法:
int[] x = ((int[])uints); Console.WriteLine(x.GetValue(0).GetType()); // System.UInt32 Console.WriteLine(x[0].GetType()); // System.Int32
所以即使访问x [0]可以返回已经转换的值,但是Array.GetValue返回已经有一个uint.
我们再做一个实验:
Console.WriteLine(x.GetType()); // System.UInt32[] Console.WriteLine(uints.GetType()); // System.UInt32[] Console.WriteLine(Object.ReferenceEquals(x,uints)); // True
这确保我们在var x =(int [])uints中的转换是一个NOP – 没有操作,它根本不做任何事情.特别是第三行显示我们得到完全相同的实例.
现在List constructor我们有线
_items = new T[count]; c.CopyTo(_items,0);
实际上抛出数组不匹配异常.
但是为什么这个例外不是早期抛出,当GetEnumerator()被调用时,我不知道自己,我预计会在行上抛出异常
x.GetEnumerator()
因为类型IEnumerable< int>和IEnumerable< uint>不兼容但没有 – 可能是因为.NET返回这里对于每个值类型数组都是相同的System.Array SZArrayEnumerator.
编辑:(以猫为例)
C#中的数组协方差保证我们可以将任何引用类型的数组分配给object [],并将Subclass []类型的数组分配给BaseClass [].值类型的情况是不同的,因为它们可以具有不同的大小和/或转换行为(uint vs int).
ToList使用内部的Array.Copy调用,当我们看到CRL:https://github.com/dotnet/coreclr/blob/32f0f9721afb584b4a14d69135bea7ddc129f755/src/classlibnative/bcltype/arraynative.cpp#L328中的Array.Copy实现时,我们看到只有当值类型具有由另一个util函数检查的兼容单位时,才能复制该数组https://github.com/dotnet/coreclr/blob/32f0f9721afb584b4a14d69135bea7ddc129f755/src/vm/invokeutil.h#L196
另一个问题是为什么它是这样实现的?