tl;dr:
- Переопределение универсального метода итератора в сконструированном производном классе приводит к выдаче
BadImageFormatException
при компиляции с Visual Studio 2010 (VS2010), независимо от версии .NET (2.0, 3.0, 3.5 или 4), платформы или конфигурации. Проблема не воспроизводится в Visual Studio 2012 (VS2012) и выше. - Содержимое базового метода (при условии, что исходный код компилируется) не имеет значения, поскольку он не выполняется.
Как этого избежать?
Описание проблемы
При переходе к in
in Main
в коде в MVCE ниже (который обычно переводит выполнение в метод итератора) _ 4_ выдается при компиляции кода в Visual Studio 2010:
но не в Visual Studio 2012 и выше:
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
Точно так же IL для метода
Main
одинаков для двоичных файлов VS2010 и 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
В двоичных файлах, скомпилированных VS2012, есть метод
<>n__FabricatedMethod4
, которого нет в VS2010:VS2012:
VS2010:
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()
Точно так же он не может просматривать содержимое метода
ScrappyDoo.GetIEnumerableItems
как C # и показывает аналогичное исключение: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>
устраняет проблему (в этом надуманном случае), но это неприемлемое решение.Проблема возникает при ориентации на .NET 2.0, .NET 3.0, .NET 3.5 и .NET 4 в VS2010. При компиляции с VS2012 и более поздними версиями целевая версия платформы не имеет значения, и код ведет себя так, как ожидалось.
Я знаю, что Visual Studio не компилирует код - он просто вызывает MSBuild (или Roslyn), но эта проблема по-прежнему существует на машине с установленными VS2010 и VS2012: при запуске кода в VS2010 проблема сохраняется, а при работе в VS2012 - нет. Установив для параметра подробности вывода сборки значение «Диагностика», я обнаружил, что как VS2010, так и VS2012 используют одни и те же двоичные файлы MSBuild в
C:\Windows\Microsoft.NET\Framework\v4.0.30319
Проблема не возникает в VS2015 (с использованием Roslyn для компиляции) - IL другой, но я думаю, этого следовало ожидать.
Мне нужно использовать Visual Studio 2010, поскольку там, где я работаю, мы занимаемся разработкой для Windows XP, которая поддерживает только версии 2010 и ниже.
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 исправлять бесполезно.
yield return
, это что-то меняет? - person Lasse V. Karlsen   schedule 20.09.2016yield break
,throw
или любой другой компилируемый код, возникнет та же проблема. Базовый метод никогда не выполняется. - person Wai Ha Lee   schedule 20.09.2016List<T>
и добавлено переопределение в его список, все будет в порядке. Думаю, это было бы не так ужасно. - person Wai Ha Lee   schedule 20.09.2016peverify
говорит о получившейся сборке? Возникает ли эта ошибка, когда код запускается вне VS 2010 или только при его отладке? Я ожидал, чтоBadImageFormatException
будет выброшен только при первой загрузке сборки, но в вашем MCVE не происходит никакой дополнительной нагрузки, если VS не делает что-то интересное в фоновом режиме. В том смысле, исчезнет ли проблема, если в параметрах отладки будет включен / выключен процесс хостинга? - person Jeroen Mostert   schedule 21.09.2016peverify
также дает исключение, очень странно. Его вся цель - диагностика плохого IL, поэтому он не должен жаловаться на плохие изображения, а должен объяснять, что с ними не так. Это говорит о том, что проблема очень низкого уровня. К сожалению, у меня больше нет VS 2010, иначе было бы интересно посмотреть. Что касается избежания проблемы, поскольку она, похоже, связана с конечным автоматом, созданным VS, отказ от использования итератора (в производном классе) должен решить ее. Если итератор не слишком сложен, реализоватьIEnumerable
самостоятельно не сложно. - person Jeroen Mostert   schedule 21.09.2016