There is some misinformation you’re likely to stumble upon via Google when searching for “C# compiler /optimize+”. Interestingly, the following snippet can be seen on various forums.
The following is a response from a developer on the C# compiler team: We get rid of unused locals (i.e., locals that are never read, even if assigned). We get rid of unreachable code. We get rid of try-catch with an empty try. We get rid of try-finally with an empty try. We get rid of try-finally with an empty finally. We optimize branches over branches: gotoif A, lab1 goto lab2: lab1: turns into: gotoif !A, lab2 lab1: We optimize branches to ret, branches to next instruction, and branches to branches.
The part in bold is incorrect.
Before I dive into a simple app that I wrote to prove that the information is incorrect, there’s a simple logical reasoning to refute the claim. Basically, a function that has had a try/finally block completely removed because of an empty try or finally will behave completely differently than one who hasn’t. Optimizing something doesn’t mean you stop doing important things to save some time
“We get rid of try-finally with an empty try”. Do they really?
For those who still aren’t convinced with the explanation above, I wrote a simple C# class and saved it in a file Test.cs. I compiled the code once from the command line without optimizations (“csc test.cs”) and once with optimizations (“csc /optimize+ test.cs”).
Here’s what Foo looked like in C#:
private void Foo()
{
int x;
try
{
}
finally
{
DoSomething();
DoSomethingElse();
}
}
Nothing much there… Just an unused variable “x”, an empty try, and some code in the finally block.
Here’s the IL without optimizations:
.method private hidebysig static void Foo() cil managed
{
// Code size 22 (0×16)
.maxstack 0
.locals init (int32 V_0) // Here’s our unused variable.
IL_0000: nop
.try
{
IL_0001: nop
IL_0002: nop
IL_0003: leave.s IL_0014
} // end .try
finally
{
IL_0005: nop
IL_0006: call void Test::DoSomething()
IL_000b: nop
IL_000c: call void Test::DoSomethingElse()
IL_0011: nop
IL_0012: nop
IL_0013: endfinally
} // end handler
IL_0014: nop
IL_0015: ret
} // end of method Test::Foo
Here’s the IL with optimizations:
.method private hidebysig static void Foo() cil managed
{
// Code size 14 (0xe)
.maxstack 0
.try
{
IL_0000: leave.s IL_000d
} // end .try
finally
{
IL_0002: call void Test::DoSomething()
IL_0007: call void Test::DoSomethingElse()
IL_000c: endfinally
} // end handler
IL_000d: ret
} // end of method Test::Foo
As you can see:
- The compiler got rid of the unused variable when optimizations were enabled
- Notice that the following declaration doesn’t appear in the second IL listing while it does in the first one:
.locals init (int32 V_0)
- Try / Finally is still there
If you’re still questioning why you would have a try/finally with an empty “try” in the first place, you might find an earlier article of mine interesting.
Hope this helps someone in an interview or something





[...] Sid of Some Creativity writes: C# compiler optimizations and empty “try” block. [...]
First of all. Thanks very much for your useful post.
I just came across your blog and wanted to drop you a note telling you how impressed I was with the
information you have posted here.
Please let me introduce you some info related to this post and I hope that it is useful for community.
There is a good C# resource site, Have alook
http://CSharpTalk.com
Thanks again
Rahul