我得到了System.ArrayTypeMismatchException: Source array type cannot be assigned to destination array type此代码段的异常:
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;
}
Run Code Online (Sandbox Code Playgroud)
然后我求助于Jon的回答为什么在C#中"int []是uint [] == true",它告诉我,由于GetArray()返回a Array,转换在运行时被推迟,而CLR允许这种转换int[] to uint[](vice versa).如果我在转换后检查值,它实际上工作正常:
foreach (var i in ((int[])units))
{
System.Console.WriteLine(i.GetType());
System.Console.WriteLine(i);
}
Run Code Online (Sandbox Code Playgroud)
我会得到:
System.Int32
-1
System.Int32
2
//skipped...
Run Code Online (Sandbox Code Playgroud)
但是我有点困惑为什么它在调用时会失败ToList(),因为下面的代码可以正常工作?
internal class Animal
{
}
internal class Cat : Animal
{
}
var cats = new Cat[] { new Cat(), new Cat() };
List<Animal> animals = ((Animal[])cats).ToList(); //no exception
Run Code Online (Sandbox Code Playgroud)
您正在尝试将数组转换为int[]它uint[].这本身是好的,因为这两个int[]和uint[]的类型的Array,和铸造类型的Array对象uint[]作为int[](或反之亦然)被允许(由于某种原因).
当您尝试将其转换为List时会出现问题.该方法试图创建一个类型列表,int因为这是您指定的类型,但这不是数组实际的类型.
这里发生了什么?让我们从foreach循环开始,C# 编译器对它们进行了大量优化,特别是当您将它们与数组一起使用时 - 它们基本上是正常的变化for(int i = 0; i < length; i++) { }- 所以示例
foreach (var i in ((int[])units))
{
System.Console.WriteLine(i.GetType());
System.Console.WriteLine(i);
}
Run Code Online (Sandbox Code Playgroud)
不能被信任,顺便说一句,foreach也可以对元素类型执行强制转换,最好尝试通用Array.GetValue方法:
int[] x = ((int[])uints);
Console.WriteLine(x.GetValue(0).GetType()); // System.UInt32
Console.WriteLine(x[0].GetType()); // System.Int32
Run Code Online (Sandbox Code Playgroud)
因此,即使访问也x[0]可以返回已经转换的值,但Array.GetValue返回的是已经存在的值uint。
我们再做一个实验:
Console.WriteLine(x.GetType()); // System.UInt32[]
Console.WriteLine(uints.GetType()); // System.UInt32[]
Console.WriteLine(Object.ReferenceEquals(x, uints)); // True
Run Code Online (Sandbox Code Playgroud)
这向我们保证了强制转换var x = (int[])uints是一个 NOP——没有操作,它什么也不做。特别是第三行向我们展示了我们得到完全相同的实例。
现在在List 构造函数中我们有几行
_items = new T[count];
c.CopyTo(_items, 0);
Run Code Online (Sandbox Code Playgroud)
实际上抛出数组不匹配异常。
但是为什么这个异常没有更早抛出,例如何时GetEnumerator()调用我自己也不知道,我预计会在线路上抛出异常
x.GetEnumerator()
Run Code Online (Sandbox Code Playgroud)
因为类型IEnumerable<int>和IEnumerable<uint>不兼容,但没有一个兼容 - 也许是因为 .NET 在此处返回的值System.Array+SZArrayEnumerator对于每个值类型数组都是相同的。
编辑:(以 Cat 为例)C# 中的数组协变确保我们可以将任何引用类型数组分配给 ,object[]并且可以将该类型数组Subclass[]分配给BaseClass[]。值类型的情况有所不同,因为它们可以具有不同的大小和/或转换行为(uintvs int)。
ToList使用内部Array.Copy调用,当我们查看Array.CopyCRL 中的实现时:https://github.com/dotnet/coreclr/blob/32f0f9721afb584b4a14d69135bea7ddc129f755/src/classlibnative/bcltype/arraynative.cpp#L328我们看到只有值类型才能复制数组具有由另一个util函数检查的兼容singess https://github.com/dotnet/coreclr/blob/32f0f9721afb584b4a14d69135bea7ddc129f755/src/vm/invokeutil.h#L196
另一个问题是为什么要这样实施......