BadImageFormatException
,无论 .NET 版本(2.0、3.0、3.5 或 4)、平台或配置如何。该问题在 Visual Studio 2012 (VS2012) 及更高版本中无法重现。如何避免这种情况?
当单步执行下面
MVCE中代码中的
in
时(通常会将执行移至迭代器方法),在 Visual Studio 中编译代码时会抛出 Main
2010:
但不适用于 Visual Studio 2012 及更高版本:
MCVE
BadImageFormatException
注意事项
ScrappyDoo.GetIEnumerableItems
.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
.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
检查二进制文件时,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
(请注意,反编译的 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>
解决了问题(在这种人为的情况下),但这不是一个可接受的解决方案。
- 它只是调用MSBuild(或Roslyn),但在安装了VS2010和VS2012的计算机上,这个问题仍然是一个问题:运行代码时在VS2010中,问题仍然存在,而在VS2012中运行时,则没有。将构建输出详细程度设置为“诊断”后,我发现 VS2010 和 VS2012 都使用相同的 MSBuild 二进制文件
IEnumerable<T>
为 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
> 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修复变得无用。
将迭代器实现移至非虚拟/重写的方法
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,所以这些都没有经过测试。