在C#中从uint []转换为int []后调用ToList()的异常

Kun*_*ken 9 c#

我得到了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)

Abi*_*n47 8

您正在尝试将数组转换为int[]uint[].这本身是好的,因为这两个int[]uint[]的类型的Array,和铸造类型的Array对象uint[]作为int[](或反之亦然)被允许(由于某种原因).

当您尝试将其转换为List时会出现问题.该方法试图创建一个类型列表,int因为这是您指定的类型,但这不是数组实际的类型.


csh*_*olk 4

这里发生了什么?让我们从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

另一个问题是为什么要这样实施......