To understand what you are doing, I suggest you make the most minimal example possible.
ilGen.Emit(OpCodes.Ldstr, "a"); ilGen.BeginExceptionBlock(); ilGen.BeginCatchBlock(typeof(Exception)); ilGen.EndExceptionBlock(); ilGen.Emit(OpCodes.Pop); ilGen.Emit(OpCodes.Ret);
After that, you can use AssemblyBuilder to dump the given code into the executable. If this is done, ildasm will show what was created.
// Code size 17 (0x11) .maxstack 2 IL_0000: ldstr "a" .try { IL_0005: leave IL_000f } // end .try catch [mscorlib]System.Exception { IL_000a: leave IL_000f } // end handler IL_000f: pop IL_0010: ret
As you can see, we get to the leave statement, which goes to pop . You can then google about leave , which states that:
A vacation instruction is similar to br, but it can be used to exit a try, filter, or catch block, while a regular instruction branch can only be used in such a block to control the transfer inside. The leave command empties the evaluation stack and ensures that the corresponding surrounding final blocks are executed.
However, why then does the next job not work?
ilGen.Emit(OpCodes.Ldstr, "a"); ilGen.BeginExceptionBlock(); ilGen.BeginCatchBlock(typeof(Exception)); ilGen.EndExceptionBlock();
I suspect that this may not be a "physical limit", but a verification problem. Let peverify ourapp.exe and see what we get:
[IL]: Error: [C:\temp\test.exe : Program::Main][offset 0x00000005] Attempt to en ter a try block with nonempty stack. 1 Error(s) Verifying C:\temp\test.exe
At this moment you can be like, wat? With a few searches, you can find error code 0x801318A9 . Quick scan through SSCLI2.0 sources:
case ReaderBaseNS::RGN_TRY:
Now, this is cool, but if you are called, you may wonder why the evaluation stack should be empty?
For this, you probably want to take a look at ECMA C # and Common Language Infrastructure Standards . I suspect you can find the reason from PartitionIII CIL.pdf