Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What does this compiler-generated enumerator mean?

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:

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

For reference, here is the 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
like image 678
Hosam Aly Avatar asked Dec 30 '22 05:12

Hosam Aly


2 Answers

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#.

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.

like image 96
Jon Skeet Avatar answered Jan 07 '23 10:01

Jon Skeet


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.

like image 44
Marc Gravell Avatar answered Jan 07 '23 11:01

Marc Gravell