views:

856

answers:

3

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
+1  A: 

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

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.

John Leidegren
You can be fairly sure that the compiler /is/ stupid because all programs are fairly stupid. (It's a corollary of Godel's incompleteness theorem)
BCS
+2  A: 

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.

Marc Gravell
+3  A: 

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.

Jon Skeet