Что означает этот перечислитель, созданный компилятором?
-
23-08-2019 - |
Вопрос
Я написал довольно сложный метод, который возвращает доходность IEnumerable<string>
, но когда я проверил вывод компилятора в Reflector, я не понял конкретную часть сгенерированной компилятором реализации IEnumerator
:
void IDisposable.Dispose()
{
switch (this.<>1__state)
{
case 1:
case 2:
case 3:
switch (this.<>1__state) // empty switch! why?!
{
}
break;
default:
return;
try // What?! AFTER return?!
{
}
finally // is the try-finally block anyhow relevant?
{
this.<>m__Finallya();
}
break;
}
this.<>m__Finally7();
}
Я предполагаю (или надеюсь), что Reflector неправильно разместил закрывающую скобку внешнего switch
, и что это должно быть сразу после return
.Тем не менее, я не понимаю, почему в случае 3 пустой переключатель или почему m__Finallya
вызывается в finally
блокировать.(Есть ли семантическая разница между обычным бегом и бегом внутри finally
блокировать?Кроме CER, которого нет в моем коде.)
Для справки, вот IL:
.method private hidebysig newslot virtual final
instance void System.IDisposable.Dispose() cil managed
{
.override [mscorlib]System.IDisposable::Dispose
// Code size 69 (0x45)
.maxstack 2
.locals init ([0] int32 CS$0$0000,
[1] int32 CS$0$0001)
IL_0000: ldarg.0
IL_0001: ldfld int32 FBD.TIP.Reader.MissingMessagesReader/'<GetMissingMessages>d__0'::'<>1__state'
IL_0006: stloc.0
IL_0007: ldloc.0
IL_0008: ldc.i4.1
IL_0009: sub
IL_000a: switch (
IL_001c,
IL_001c,
IL_001c)
IL_001b: ret
IL_001c: ldarg.0
IL_001d: ldfld int32 FBD.TIP.Reader.MissingMessagesReader/'<GetMissingMessages>d__0'::'<>1__state'
IL_0022: stloc.1
IL_0023: ldloc.1
IL_0024: ldc.i4.2
IL_0025: sub
IL_0026: switch (
IL_0035,
IL_0035)
IL_0033: br.s IL_003e
.try
{
IL_0035: leave.s IL_003e
} // end .try
finally
{
IL_0037: ldarg.0
IL_0038: call instance void FBD.TIP.Reader.MissingMessagesReader/'<GetMissingMessages>d__0'::'<>m__Finallya'()
IL_003d: endfinally
} // end handler
IL_003e: ldarg.0
IL_003f: call instance void FBD.TIP.Reader.MissingMessagesReader/'<GetMissingMessages>d__0'::'<>m__Finally7'()
IL_0044: ret
} // end of method '<GetMissingMessages>d__0'::System.IDisposable.Dispose
Решение
Это просто отражатель, изо всех сил пытающийся не отставать от сгенерированного IL (поскольку блоки итератора не обязательно должны относиться к «нормальному» C#, если они являются допустимыми IL).В частности, ret
это после finally
блокировать.
Другие советы
Вы не показали, как выглядит исходный блок итератора, но мой опыт работы с Reflector и кодом, генерируемым компилятором, показывает, что его не всегда удается полностью точно декомпилировать, поскольку компилятор использует некоторый IL, который не имеет эквивалента в С#.
у меня есть статья о реализации блока итератора что может вам немного помочь, но я бы не стал слишком беспокоиться о том, как выглядит скомпилированный код.В некоторых случаях компилятор C# почти наверняка генерирует ненужный код, поскольку это упрощает компилятор.Блоки итераторов, должно быть, очень сложно получить правильно (это может оказаться очень сложным из-за блоковfinally и удаления итераторов), поэтому я думаю, что разумно просто доверить JIT оптимизацию ненужных битов, таких как переключатель/регистр, в вашем сгенерированном коде.
Я мог бы возразить, что компилятор C# глуп (вероятно, он немного глуп).Также вполне возможно, что этот код будет выглядеть совсем по-другому, если его обработать во время выполнения (весь этот неприятный мусор будет опущен).
В любом случае, возможно, вы знакомы с конечными автоматами?Когда вы пишете генераторы на C# (доходность), вы указываете компилятору создать анонимный тип, который реализует этот генератор как конечный автомат.Это хороший формальный подход, который должен быть проверяемым.Вероятно, именно поэтому он выглядит так, как есть.