Enumerable.Cast 扩展方法无法从int转换为long,为什么?

可能重复:
令人费解的Enumerable.Cast InvalidCastException

嗨,

我刚刚注意到Enumerable.Cast扩展方法有些奇怪……似乎它无法从intlong ,即使这个转换是完全合法的。

以下代码因InvalidCastException失败:

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

但是这个代码,我认为是相同的,确实有效:

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

谁能解释这种行为? 我用Reflector查看了Cast方法的代码,但是Reflector无法解释迭代器块,所以很难理解……

Cast的相关行:

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

我们可以使用以下代码模仿:

 int foo = 1; long bar = Cast(foo); //oh noes! T Cast(object input) { return (T)input; } 

哪个也失败了。 这里的关键是,在演员阵容中,它是一个对象。 不是int。 这失败了,因为我们只能从对象中取消包装到我们想要的确切类型 。 我们要从物体出发 – 这可能是盒装长,但事实并非如此。 这是一个盒装的int。 Eric Lippert在他的博客上对此进行了讨论 :

我们已经决定取消装箱只能拆箱到确切的类型。 如果你想调用那个goo的慢速方法,它可用 – 你可以随时调用Convert …

在你的代码中,你没有处理盒装的int(一个对象),你有一个int。

与大多数其他LINQ扩展方法不同, Cast扩展了非通用IEnumerable接口,而不是IEnumerable

这意味着Range调用生成的int值由Cast调用的底层枚举器装箱,然后它会尝试将它们转换为long并失败,因为值只能取消装箱到完全相同的类型。

您可以通过显式装箱int值来模仿第二个循环中的相同exception行为:

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

问题是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/d__aa::<>7__wrapac L_0043: callvirt instance object [mscorlib]System.Collections.IEnumerator::get_Current() L_0048: stfld object System.Linq.Enumerable/d__aa::5__ab L_004d: ldarg.0 L_004e: ldarg.0 L_004f: ldfld object System.Linq.Enumerable/d__aa::5__ab L_0054: unbox.any !TResult 

解决方法是使用Select()