这个编译器生成的枚举器是什么意思?

时间:2022-09-06 12:44:00

I wrote a fairly complex method that yield-returns IEnumerable<string>, but when I inspected the compiler output in Reflector, I didn't understand a specific part of the compiler-generated implementation of IEnumerator:

我写了一个相当复杂的方法,yield-returns IEnumerable ,但是当我在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();
}

I'm guessing (or hoping) that Reflector misplaced the closing brace of the outer switch, and that it should be directly after the return. Still, I don't understand why there is an empty switch in case 3, or why m__Finallya is being called in a finally block. (Is there a semantic difference between running normally and inside a finally block? Other than CER's, which I don't have in my code.)

我猜测(或希望)Reflector错放了外部开关的右括号,并且它应该在返回后直接放置。不过,我不明白为什么在案例3中有一个空开关,或者为什么在finally块中调用m__Finallya。 (正常运行和最终块内部之间是否存在语义差异?除了CER之外,我的代码中没有。)

For reference, here is the IL:

作为参考,这是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

3 个解决方案

#1


That is simply reflector struggling to keep up with the IL that has been generated (since iterator blocks don't have to relate to "normal" C# as long as they are valid IL). In particular, the ret is after the finally block.

这只是反射器努力跟上已生成的IL(因为迭代器块不必与“正常”C#相关,只要它们是有效的IL)。特别是,ret在finally块之后。

#2


You haven't shown what your original iterator block looks like, but my experience of Reflector and compiler-generated code is that it doesn't always manage to decompile entirely accurately, because the compiler uses some IL which doesn't have an equivalent in C#.

你还没有展示你的原始迭代器块是什么样的,但是我对Reflector和编译器生成的代码的体验是它并不总是能够完全准确地反编译,因为编译器使用了一些没有等效的IL。 C#。

I've got an article about iterator block implementation which may help you a bit, but I wouldn't worry too much about what the compiled code looks like. In some cases the C# compiler is almost certainly generating unnecessary code on the grounds that that keeps the compiler simpler. Iterator blocks must have been very tricky to get right (it can get very complicated, with finally blocks and iterator disposal) so I think it's reasonable to just trust the JIT to optimise away the unnecessary bits like the switch/case in your generated code.

我有一篇关于迭代器块实现的文章可能对你有所帮助,但我不会太担心编译代码的样子。在某些情况下,C#编译器几乎肯定会生成不必要的代码,因为这样可以使编译器更简单。迭代器块必须非常难以正确(它可能变得非常复杂,最后使用块和迭代器处理)所以我认为仅仅信任JIT来优化掉生成的代码中的开关/大小写等不必要的位是合理的。

#3


I could argue that the C# compiler is stupid, (it's probably is a little bit stupid). It's also quite possible that this code looks very different when jitted by the run-time (all that nasty garbage gets omitted).

我可以说C#编译器是愚蠢的,(它可能有点愚蠢)。当运行时进行jitted时,这个代码看起来很不一样(所有讨厌的垃圾都被省略了)。

Anyhow, you maybe familiar with state machines? When you write generators in C# (yield stuff) you tell the compiler to emit a anonymous type which implements this generator as a state machine. This is a nice formal approach which is meant to be verifiable. That's probably the reasons why it looks the way it does.

无论如何,你可能熟悉状态机吗?当你用C#编写生成器(yield stuff)时,你告诉编译器发出一个匿名类型,它将这个生成器实现为状态机。这是一个很好的正式方法,可以验证。这可能是它看起来如此的原因。

#1


That is simply reflector struggling to keep up with the IL that has been generated (since iterator blocks don't have to relate to "normal" C# as long as they are valid IL). In particular, the ret is after the finally block.

这只是反射器努力跟上已生成的IL(因为迭代器块不必与“正常”C#相关,只要它们是有效的IL)。特别是,ret在finally块之后。

#2


You haven't shown what your original iterator block looks like, but my experience of Reflector and compiler-generated code is that it doesn't always manage to decompile entirely accurately, because the compiler uses some IL which doesn't have an equivalent in C#.

你还没有展示你的原始迭代器块是什么样的,但是我对Reflector和编译器生成的代码的体验是它并不总是能够完全准确地反编译,因为编译器使用了一些没有等效的IL。 C#。

I've got an article about iterator block implementation which may help you a bit, but I wouldn't worry too much about what the compiled code looks like. In some cases the C# compiler is almost certainly generating unnecessary code on the grounds that that keeps the compiler simpler. Iterator blocks must have been very tricky to get right (it can get very complicated, with finally blocks and iterator disposal) so I think it's reasonable to just trust the JIT to optimise away the unnecessary bits like the switch/case in your generated code.

我有一篇关于迭代器块实现的文章可能对你有所帮助,但我不会太担心编译代码的样子。在某些情况下,C#编译器几乎肯定会生成不必要的代码,因为这样可以使编译器更简单。迭代器块必须非常难以正确(它可能变得非常复杂,最后使用块和迭代器处理)所以我认为仅仅信任JIT来优化掉生成的代码中的开关/大小写等不必要的位是合理的。

#3


I could argue that the C# compiler is stupid, (it's probably is a little bit stupid). It's also quite possible that this code looks very different when jitted by the run-time (all that nasty garbage gets omitted).

我可以说C#编译器是愚蠢的,(它可能有点愚蠢)。当运行时进行jitted时,这个代码看起来很不一样(所有讨厌的垃圾都被省略了)。

Anyhow, you maybe familiar with state machines? When you write generators in C# (yield stuff) you tell the compiler to emit a anonymous type which implements this generator as a state machine. This is a nice formal approach which is meant to be verifiable. That's probably the reasons why it looks the way it does.

无论如何,你可能熟悉状态机吗?当你用C#编写生成器(yield stuff)时,你告诉编译器发出一个匿名类型,它将这个生成器实现为状态机。这是一个很好的正式方法,可以验证。这可能是它看起来如此的原因。