Подтвердить что ты не робот

Enumerable.Cast <T> метод расширения не может отбрасывать из int в long, почему?

Возможный дубликат:
Puzzling Enumerable.Cast InvalidCastException

Привет,

Я заметил что-то довольно странное в методе расширения Enumerable.Cast<T>... Похоже, что он не может отбрасывать от int до long, хотя этот актер совершенно легален.

Следующий код не работает с InvalidCastException:

        foreach (var item in Enumerable.Range(0,10).Cast<long>())
        {
            Console.WriteLine(item);
        }

Но этот код, который я предположил эквивалентным, работает:

        foreach (var item in Enumerable.Range(0,10).Select(i => (long)i))
        {
            Console.WriteLine(item);
        }

Может ли кто-нибудь объяснить это поведение? Я посмотрел на код метода Cast с Reflector, но Reflector не может интерпретировать блоки итераторов, поэтому его довольно сложно понять...

4b9b3361

Ответ 1

Соответствующая строка в Cast:

 this.<>2__current = (TResult)this.<obj>5__ab;

Мы можем имитировать это, используя следующий код:

int foo = 1;
long bar = Cast<long>(foo); //oh noes!

T Cast<T>(object input)
{
    return (T)input;
}

Который также терпит неудачу. Ключевым моментом здесь является то, что в момент броска это объект. Не int. Это терпит неудачу, потому что мы можем только удалить из объекта точный тип, который нам нужен. Мы переходим от объекта, который может быть коротким, но это не так. Это в штучной упаковке. Эрик Липперт обсуждал это в своем блоге:

weve решила, что unboxing может только отправлять сообщения в нужный тип. Если вы хотите вызвать медленный метод, который делает все это, он доступен - вы всегда можете вызвать Convert...

В вашем коде, который работает, вы не имеете дело с коробочным int (объектом), у вас есть int.

Ответ 2

В отличие от большинства других методов расширения LINQ, Cast расширяет не общий IEnumerable, а не IEnumerable<T>.

Это означает, что значения int, сгенерированные вызовом Range, помещаются в нужный перечислитель вызова Cast, который затем пытается передать их в long и терпит неудачу, поскольку значения могут быть только распакованы в точно такой же тип.

Вы можете воспроизвести одно и то же поведение исключения во втором цикле, явно указав значения int:

foreach (var item in Enumerable.Range(0, 10).Select(i => (long)(object)i))
{
    Console.WriteLine(item);
}

Ответ 3

Проблема заключается в том, что CastIterator MoveNext помещает текущее значение и пытается удалить его с целевым типом (где значение в боксе не соответствует правильному типу), поэтому при сбое проверки типа распаковывается.

Справочная информация:

http://msdn.microsoft.com/en-us/library/system.reflection.emit.opcodes.unbox_any.aspx

    L_003c: ldarg.0 
    L_003d: ldarg.0 
    L_003e: ldfld class [mscorlib]System.Collections.IEnumerator System.Linq.Enumerable/<CastIterator>d__aa<!TResult>::<>7__wrapac
    L_0043: callvirt instance object [mscorlib]System.Collections.IEnumerator::get_Current()
    L_0048: stfld object System.Linq.Enumerable/<CastIterator>d__aa<!TResult>::<obj>5__ab
    L_004d: ldarg.0 
    L_004e: ldarg.0 
    L_004f: ldfld object System.Linq.Enumerable/<CastIterator>d__aa<!TResult>::<obj>5__ab
    L_0054: unbox.any !TResult

Обходным путем является использование Select()