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

时间:2016-11-15 07:22:26

标签: 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;
}

然后我在Why does "int[] is uint[] == true" in C#中求助于Jon的回答,它告诉我由于GetArray()返回Array,转换在运行时被推迟,而CLR允许这样做那种int[] to uint[](vice versa)演员。如果我在转换后检查值,它实际上工作正常:

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

2 个答案:

答案 0 :(得分:8)

您正在尝试将数组转换为$db['develop'] = array( 'dsn' => '', 'hostname' => 'xxxxxxxxxxx.us-east-1.rds.amazonaws.com', 'username' => 'xxxxxxxx', 'password' => 'xxxxxxxx', 'database' => 'xxxxxxx', 'dbdriver' => 'mysqli', 'dbprefix' => '', 'pconnect' => FALSE, 'db_debug' => (ENVIRONMENT !== 'production'), 'cache_on' => FALSE, 'cachedir' => '', // 'char_set' => 'utf8', // 'dbcollat' => 'utf8_general_ci', 'char_set' => 'utf8mb4', 'dbcollat' => 'utf8mb4_unicode_ci', 'swap_pre' => '', 'encrypt' => FALSE, 'compress' => FALSE, 'stricton' => FALSE, 'failover' => array(), 'save_queries' => TRUE int[]。这本身很好,因为uint[]int[]都是uint[]类型,并且将Array类型的Array对象强制转换为uint[](或者int[]允许(由于某种原因)。

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

答案 1 :(得分: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);
}

无法信任,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 - 没有操作,它什么都不做。特别是第3行向我们展示了我们得到完全相同的实例。

现在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[]。值类型的情况不同,因为它们可以具有不同的大小和/或转化行为(uintint)。

ToList使用内部Array.Copy调用,当我们查看CRL中的Array.Copy实现时:https://github.com/dotnet/coreclr/blob/32f0f9721afb584b4a14d69135bea7ddc129f755/src/classlibnative/bcltype/arraynative.cpp#L328我们看到只有在值类型具有兼容的singess时才能复制该数组由另一个util函数https://github.com/dotnet/coreclr/blob/32f0f9721afb584b4a14d69135bea7ddc129f755/src/vm/invokeutil.h#L196

检查

另一个问题是为什么以这种方式实施......