Ответ 1
Это из-за оптимизации, которая, к сожалению, немного нарушена перед неожиданными преобразованиями CLR.
На уровне CLR есть ссылочное преобразование от Foo[]
до int[]
- вам вообще не нужно бросать каждый объект. Это неверно на уровне С#, но оно находится на уровне CLR.
Теперь Cast<>
содержит оптимизацию, чтобы сказать "если я уже имею дело с коллекцией правильного типа, я могу просто вернуть ту же ссылку назад" - эффективно:
if (source is IEnumerable<T>)
{
return source;
}
Итак a.Cast<int>
возвращает a
, который является Foo[]
. Это прекрасно, когда вы передаете его на PrintGeneric
, потому что тогда есть неявное преобразование в T
в цикле foreach
. Компилятор знает, что тип IEnumerator<T>.Current
равен T
, поэтому соответствующий слот стека имеет тип T
. Скомпилированный код JIT-аргумента для каждого типа будет "делать правильную вещь" при обработке значения как int
, а не как Foo
.
Однако, когда вы передаете массив как IEnumerable
, свойство Current
в IEnumerator
имеет тип object
, поэтому каждое значение будет помещено в поле и передано в Console.WriteLine(object)
- и вставляется в коробку объект будет иметь тип Foo
, а не int
.
Вот пример кода, чтобы показать первую часть этого - остальное немного проще понять, я считаю, после того, как вы прошли мимо:
using System;
using System.Linq;
enum Foo { }
class Test
{
static void Main()
{
Foo[] x = new Foo[10];
// False because the C# compiler is cocky, and "optimizes" it out
Console.WriteLine(x is int[]);
// True because when we put a blindfold in front of the compiler,
// the evaluation is left to the CLR
Console.WriteLine(((object) x) is int[]);
// Foo[] and True because Cast returns the same reference back
Console.WriteLine(x.Cast<int>().GetType());
Console.WriteLine(ReferenceEquals(x, x.Cast<int>()));
}
}
Вы увидите то же самое, если попытаетесь пройти между uint[]
и int[]
. Кстати,