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

Wai*_*Lee 5 c# visual-studio-2010 compiler-bug visual-studio-2012

tl; dr:

  • BadImageFormatException无论使用.NET版本(2.0、3.0、3.5或4),平台或配置如何,在构造的派生类中重写通用迭代器方法都将导致在使用Visual Studio 2010(VS2010)进行编译时引发该错误。在Visual Studio 2012(VS2012)及更高版本中,该问题无法重现。
  • 基本方法的内容(由源编译提供)无关紧要,因为它不执行。

如何避免这种情况?


问题描述

当步入inMain在代码MVCE下方(这通常会执行移动到迭代法),BadImageFormatException当该代码在Visual Studio 2010被编译被抛出:

VS2010中的BadImageFormatException

但不在Visual Studio 2012及更高版本中:

VS2012中的BadImageFormatException


MCVE

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

注意事项

  • 使用ILSpy检查代码时,ScrappyDoo.GetIEnumerableItemsVS2010和VS2012二进制文件的编译IL 相同:

    .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
    
    Run Code Online (Sandbox Code Playgroud)
  • 同样,Main对于VS2010和VS2012二进制文件,该方法的IL 相同:

    .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
    
    Run Code Online (Sandbox Code Playgroud)
  • 在VS2012编译的二进制文件中,有一种方法,<>n__FabricatedMethod4在VS2010中没有出现:

    VS2012

    Visual Studio 2012中的FabricatedMethod

    VS2010

    Visual Studio 2010中的FabricatedMethod

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

    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()
    
    Run Code Online (Sandbox Code Playgroud)

    同样,它无法以ScrappyDoo.GetIEnumerableItemsC#形式查看方法的内容,并显示类似的异常:

    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
    
    Run Code Online (Sandbox Code Playgroud)
  • 使用DotPeek检查二进制文件时,VS2010和VS2012编译的代码的反编译代码在foreach语句的表达式上有所不同:

    VS2010

    // ISSUE: reference to a compiler-generated method
    foreach (object obj in (IEnumerable<object>) this.<>n__FabricatedMethod4())
      yield return obj;
    
    Run Code Online (Sandbox Code Playgroud)

    VS2012(请注意,反编译的C#与源代码相同,正如预期的那样):

    foreach (object obj in base.GetIEnumerableItems())
      yield return obj;
    
    Run Code Online (Sandbox Code Playgroud)
  • 通过将方法更改为属性,或通过向基数或替代中添加更多逻辑,无法解决问题。

  • 将基本方法更改为return IEnumerable<object>而不是IEnumerable<T>解决该问题(在这种情况下),但这不是可接受的解决方案。

  • 在VS2010中定位.NET 2.0,.NET 3.0,.NET 3.5和.NET 4时,会发生此问题。使用VS2012及更高版本进行编译时,目标框架版本无关紧要,并且代码的行为符合预期。

  • 我知道Visual Studio不会编译代码 -它只是调用MSBuild(或Roslyn),但是在安装了VS2010和VS2012的计算机上,此问题仍然是一个问题:在VS2010中运行代码时,问题仍然存在,何时在VS2012中运行它不是。将生成输出的详细程度设置为“诊断”后,我发现VS2010和VS2012都在使用相同的MSBuild二进制文件

    C:\Windows\Microsoft.NET\Framework\v4.0.30319
    
    Run Code Online (Sandbox Code Playgroud)
  • 这个问题在VS2015中没有出现(使用Roslyn进行编译)-IL有所不同,但是我想这是可以预期的。

  • 我需要使用Visual Studio 2010,因为在我工作的地方,我们在仅支持2010及更低版本的Windows XP上进行了一些开发。

  • PEVerify为VS2010编译的代码提供以下输出:

    > 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
    
    Run Code Online (Sandbox Code Playgroud)

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

    > 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.
    
    Run Code Online (Sandbox Code Playgroud)
  • 从命令提示符运行VS2010编译的代码时,将产生以下输出:

    > 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
    
    Run Code Online (Sandbox Code Playgroud)

我的实际问题

有谁知道这是为什么,以及如何避免它?对于我的实际用例,基础迭代器中没有任何项,因此我制作了基础方法abstract并使所有派生类都覆盖了它,但是这种情况随时可能发生变化,从而使黑客修复程序无用。