簡體   English   中英

使用Visual Studio 2010編譯時,重寫泛型迭代器會導致BadImageFormatException

[英]Overriding generic iterator results in BadImageFormatException when compiled with Visual Studio 2010

TL;博士:

  • 在構造的派生類中重寫泛型迭代器方法會導致在使用Visual Studio 2010(VS2010)編譯時拋出BadImageFormatException ,而不管.NET版本(2.0,3.0,3.5或4),平台或配置如何。 該問題在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;
    }
}

值得注意的事情

  • 當檢查與代碼ILSpy ,已編譯的IL 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 
  • 同樣,對於VS2010和VS2012二進制文件, Main方法的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 
  • 在VS2012編譯的二進制文件中,有一個方法<>n__FabricatedMethod4 ,它沒有出現在VS2010中:

    VS2012

    Visual Studio 2012中的FabricatedMethod

    VS2010

    Visual Studio 2010中的FabricatedMethod

    ILSpy無法檢查IL中VS2010二進制文件中的“已損壞”方法,並遇到以下異常:

     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() 

    同樣,它無法以C#的ScrappyDoo.GetIEnumerableItems查看ScrappyDoo.GetIEnumerableItems方法的內容並顯示類似的異常:

     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 
  • 使用DotPeek檢查二進制文件時,VS2010和VS2012編譯代碼的反編譯代碼在foreach語句的表達式中有所不同:

    VS2010

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

    VS2012 (請注意,反編譯的C#與源相同,如預期的那樣):

     foreach (object obj in base.GetIEnumerableItems()) yield return obj; 
  • 通過將方法更改為屬性,或通過向基礎或覆蓋中添加更多邏輯,無法解決問題。

  • 更改基本方法以返回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中運行它沒有。 在將構建輸出詳細程度設置為Diagnostic時,我發現VS2010和VS2012都在使用相同的MSBuild二進制文件

     C:\\Windows\\Microsoft.NET\\Framework\\v4.0.30319 
  • 這個問題沒有出現在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 

    而對於通過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. 
  • 從命令提示符運行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 

我的實際問題

有誰知道這是為什么,以及如何避免它? 對於我的實際用例,基類中的迭代器中沒有項目,因此我將基本方法abstract ,並使所有派生類覆蓋它,但這可能會在任何時候發生變化,使得 hack 修復無用。

解決這個問題的三個建議不需要完全放棄迭代器,所有這些都依賴於讓VS看到基數和派生返回類型的“差異”,這似乎是麻煩的根源。

將迭代器實現移動到非虛擬/重寫的方法

public override IEnumerable<object> GetIEnumerableItems()
{
    return getIEnumerableItems();
}

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

以明顯的方式將基本調用移出迭代器

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

IEnumerable<object> baseItems() 
{
    return base.GetIEnumerableItems();
}

這可能會被內聯阻礙,但我不認為編譯器會打擾(傳統上這樣的事情留給IL級別)。

將基本調用移出迭代器,涉及方式

public override IEnumerable<object> GetIEnumerableItems()
{
    return getIEnumerableItems(base.GetIEnumerableItems());
}

IEnumerable<object> getIEnumerableItems(IEnumerable<object> baseItems) 
{
    foreach ( var item in baseItems )
        yield return item;
}

免責聲明:由於缺少VS 2010安裝,所有這些都未經過測試。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM