使用 Visual Studio 2010 编译时,重写通用迭代器会导致 BadImageFormatException

问题描述 投票:0回答:2

tl;博士:

  • 在构造的派生类中重写通用迭代器方法会导致在使用 Visual Studio 2010 (VS2010) 编译时抛出
    BadImageFormatException
    ,无论 .NET 版本(2.0、3.0、3.5 或 4)、平台或配置如何。该问题在 Visual Studio 2012 (VS2012) 及更高版本中无法重现。
  • 基本方法的内容(只要源代码编译)是不相关的,因为它没有被执行。

如何避免这种情况?


问题描述

当单步执行下面

MVCE
中代码中的
in
时(通常会将执行移至迭代器方法),在 Visual Studio 中编译代码时会抛出 Main
 2010:

但不适用于 Visual Studio 2012 及更高版本:

MCVE

BadImageFormatException


注意事项

    使用
  • ILSpy

    检查代码时,public class Program { public static void Main(string[] args) { foreach ( var item in new ScrappyDoo().GetIEnumerableItems() ) Console.WriteLine(item.ToString()); } } public class ScoobyDoo<T> where T : new() { public virtual IEnumerable<T> GetIEnumerableItems() { yield return new T(); } } public class ScrappyDoo : ScoobyDoo<object> { public override IEnumerable<object> GetIEnumerableItems() { foreach ( var item in base.GetIEnumerableItems() ) yield return item; } } 的编译 IL 对于 VS2010 和 VS2012 二进制文件是相同的:

    
    
    ScrappyDoo.GetIEnumerableItems

  • 同样,对于 VS2010 和 VS2012 二进制文件,
  • .method public hidebysig virtual instance class [mscorlib]System.Collections.Generic.IEnumerable`1<object> GetIEnumerableItems () cil managed { // Method begins at RVA 0x244c // Code size 21 (0x15) .maxstack 2 .locals init ( [0] class MysteryMachine.ScrappyDoo/'<GetIEnumerableItems>d__0', [1] class [mscorlib]System.Collections.Generic.IEnumerable`1<object> ) IL_0000: ldc.i4.s -2 IL_0002: newobj instance void MysteryMachine.ScrappyDoo/'<GetIEnumerableItems>d__0'::.ctor(int32) IL_0007: stloc.0 IL_0008: ldloc.0 IL_0009: ldarg.0 IL_000a: stfld class MysteryMachine.ScrappyDoo MysteryMachine.ScrappyDoo/'<GetIEnumerableItems>d__0'::'<>4__this' IL_000f: ldloc.0 IL_0010: stloc.1 IL_0011: br.s IL_0013 IL_0013: ldloc.1 IL_0014: ret } // end of method ScrappyDoo::GetIEnumerableItems

    方法的 IL 是相同的:

    
    
    Main

  • 在VS2012编译的二进制文件中,有一个方法,
  • .method public hidebysig static void Main ( string[] args ) cil managed { // Method begins at RVA 0x2050 // Code size 69 (0x45) .maxstack 2 .entrypoint .locals init ( [0] object item, [1] class [mscorlib]System.Collections.Generic.IEnumerator`1<object> CS$5$0000, [2] bool CS$4$0001 ) IL_0000: nop IL_0001: nop IL_0002: newobj instance void MysteryMachine.ScrappyDoo::.ctor() IL_0007: callvirt instance class [mscorlib]System.Collections.Generic.IEnumerable`1<!0> class MysteryMachine.ScoobyDoo`1<object>::get_GetIEnumerableItems() IL_000c: callvirt instance class [mscorlib]System.Collections.Generic.IEnumerator`1<!0> class [mscorlib]System.Collections.Generic.IEnumerable`1<object>::GetEnumerator() IL_0011: stloc.1 .try { IL_0012: br.s IL_0027 // loop start (head: IL_0027) IL_0014: ldloc.1 IL_0015: callvirt instance !0 class [mscorlib]System.Collections.Generic.IEnumerator`1<object>::get_Current() IL_001a: stloc.0 IL_001b: ldloc.0 IL_001c: callvirt instance string [mscorlib]System.Object::ToString() IL_0021: call void [mscorlib]System.Console::WriteLine(string) IL_0026: nop IL_0027: ldloc.1 IL_0028: callvirt instance bool [mscorlib]System.Collections.IEnumerator::MoveNext() IL_002d: stloc.2 IL_002e: ldloc.2 IL_002f: brtrue.s IL_0014 // end loop IL_0031: leave.s IL_0043 } // end .try finally { IL_0033: ldloc.1 IL_0034: ldnull IL_0035: ceq IL_0037: stloc.2 IL_0038: ldloc.2 IL_0039: brtrue.s IL_0042 IL_003b: ldloc.1 IL_003c: callvirt instance void [mscorlib]System.IDisposable::Dispose() IL_0041: nop IL_0042: endfinally } // end handler IL_0043: nop IL_0044: ret } // end of method Program::Main

    ,在VS2010中没有出现:

    
    

    VS2012

    VS2010

    ILSpy 无法检查 VS2010 二进制文件中“损坏”方法的 IL,并遇到以下异常:

    <>n__FabricatedMethod4

    同样,无法像 C# 那样查看 
    System.NullReferenceException: Object reference not set to an instance of an object. at ICSharpCode.Decompiler.Disassembler.DisassemblerHelpers.WriteTo(TypeReference type, ITextOutput writer, ILNameSyntax syntax) at ICSharpCode.Decompiler.Disassembler.DisassemblerHelpers.WriteTo(TypeReference type, ITextOutput writer, ILNameSyntax syntax) at ICSharpCode.Decompiler.Disassembler.ReflectionDisassembler.DisassembleMethodInternal(MethodDefinition method) at ICSharpCode.ILSpy.TextView.DecompilerTextView.DecompileNodes(DecompilationContext context, ITextOutput textOutput) at ICSharpCode.ILSpy.TextView.DecompilerTextView.<>c__DisplayClass31_0.<DecompileAsync>b__0()

    方法的内容,并显示类似的异常:

    
    
    ScrappyDoo.GetIEnumerableItems

  • 使用
  • DotPeek

    检查二进制文件时,VS2010和VS2012编译的代码的反编译代码在ICSharpCode.Decompiler.DecompilerException: Error decompiling System.Collections.Generic.IEnumerable`1<System.Object> MysteryMachine.ScrappyDoo::GetIEnumerableItems() ---> System.NullReferenceException: Object reference not set to an instance of an object. // stack trace elided 语句的表达式上有所不同:

    
    

    VS2010

    foreach

    VS2012

    (请注意,反编译的 C# 与源代码相同,正如预期的那样): // ISSUE: reference to a compiler-generated method foreach (object obj in (IEnumerable<object>) this.<>n__FabricatedMethod4()) yield return obj;

  • 通过将方法更改为属性,或在基础或重写中添加更多逻辑,无法解决该问题。
  • 更改基本方法以返回
  • foreach (object obj in base.GetIEnumerableItems()) yield return obj;

    而不是

    IEnumerable<object>
    解决了问题(在这种人为的情况下),但这不是一个可接受的解决方案。
    
    

  • 在 VS2010 中针对 .NET 2.0、.NET 3.0、.NET 3.5 和 .NET 4 时会出现此问题。当使用 VS2012 及更高版本编译时,目标框架版本无关,代码的行为符合预期。
  • 我知道
  • Visual Studio不会编译代码

    - 它只是调用MSBuild(或Roslyn),但在安装了VS2010和VS2012的计算机上,这个问题仍然是一个问题:运行代码时在VS2010中,问题仍然存在,而在VS2012中运行时,则没有。将构建输出详细程度设置为“诊断”后,我发现 VS2010 和 VS2012 都使用相同的 MSBuild 二进制文件 IEnumerable<T>

  • 这个问题在VS2015中没有出现(使用Roslyn编译)——IL是不同的,但我想这是可以预料的。
  • 我需要使用 Visual Studio 2010,因为我工作的地方,我们在 Windows XP 上进行一些开发,仅支持 2010 及以下版本。
  • PEVerify

    为 VS2010 编译的代码提供以下输出: C:\Windows\Microsoft.NET\Framework\v4.0.30319

    而对于通过 VS2012 及以上版本编译的二进制文件,结果如预期:

    > peverify MysteryMachine2010.exe Microsoft (R) .NET Framework PE Verifier. Version 4.0.30319.0 Copyright (c) Microsoft Corporation. All rights reserved. [IL]: Error: [MysteryMachine2010.exe : MysteryMachine.ScrappyDoo::<>n__FabricatedMethod4] [HRESULT 0x8007000B] - An attempt was made to load a program with an incorrect format. [IL]: Error: [MysteryMachine2010.exe : MysteryMachine.ScrappyDoo+<getIEnumerableItems>d__0::MoveNext] [HRESULT 0x8007000B] - An attempt was made to load a program with an incorrect format. 2 Error(s) Verifying MysteryMachine2010.exe

  • 从命令提示符运行 VS2010 编译的代码时会产生以下输出:
  • > peverify "MysteryMachine2012.exe" Microsoft (R) .NET Framework PE Verifier. Version 4.0.30319.0 Copyright (c) Microsoft Corporation. All rights reserved. All Classes and Methods in MysteryMachine2012.exe Verified.

    
    
    
我的实际问题

有人知道这是为什么,以及如何避免吗?对于我的实际用例,基础中的迭代器中没有项目,因此我创建了基本方法

> MysteryMachine2010.exe Unhandled Exception: System.BadImageFormatException: An attempt was made to load a program with an incorrect format. (Exception from HRESULT: 0x8007000B) at MysteryMachine.ScrappyDoo.<getIEnumerableItems>d__0.MoveNext() at MysteryMachine.Program.Main(String[] args) in MysteryMachine\Program.cs:line 11

并让所有派生类覆盖它,但这可能随时发生变化,从而使

hack
修复变得无用。

c# visual-studio-2010 visual-studio-2012 compiler-bug
2个回答
3
投票

将迭代器实现移至非虚拟/重写的方法

abstract

将基本调用移出迭代器,这是显而易见的方法

public override IEnumerable<object> GetIEnumerableItems() { return getIEnumerableItems(); } IEnumerable<object> getIEnumerableItems() { foreach ( var item in base.GetIEnumerableItems() ) yield return item; }

这可能会被内联所阻碍,但我认为编译器不会打扰(传统上这些事情都留给 IL 级别)。

将基本调用移出迭代器,涉及方式

public override IEnumerable<object> GetIEnumerableItems() { foreach ( var item in baseItems() ) { yield return item; } } IEnumerable<object> baseItems() { return base.GetIEnumerableItems(); }

免责声明:由于没有安装 VS 2010,所以这些都没有经过测试。


0
投票
在Wordpress仪表板菜单中重命名插件名称

你到底以为你是谁,如果你不同意,就给它一个不喜欢,不要删除它,你这个无用的四眼书呆子***** 笨蛋。学会尊重******弱智的言论

© www.soinside.com 2019 - 2024. All rights reserved.