Enumerable.Cast 扩展方法无法从int转换为long,为什么?
可能重复:
令人费解的Enumerable.Cast InvalidCastException
嗨,
我刚刚注意到Enumerable.Cast
扩展方法有些奇怪……似乎它无法从int
为long
,即使这个转换是完全合法的。
以下代码因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()