3

I would expect the following two implementations of MyMethod to behave exactly the same. Do they? If not, this could already be my wrong assumption:

First:

public int MyMethod(int x)
{
    try
    {
        return x + 8;
    }
    catch
    {
        throw;
    }
}

Second:

public int MyMethod(int x)
{
    return x + 8;
}

So, I would assume the compiler will optimize this out, i.e. remove the unnecessary try/catch block of the first implementation (when in Release Mode). As it turns out, it doesn't - here is the generated IL for the two code samples:

First:

.method public hidebysig instance int32  MyMethod(int32 x) cil managed
{
  // Code size       11 (0xb)
  .maxstack  2
  .locals init ([0] int32 CS$1$0000)
  .try
  {
    IL_0000:  ldarg.1
    IL_0001:  ldc.i4.8
    IL_0002:  add
    IL_0003:  stloc.0
    IL_0004:  leave.s    IL_0009
  }  // end .try
  catch [mscorlib]System.Object 
  {
    IL_0006:  pop
    IL_0007:  rethrow
  }  // end handler
  IL_0009:  ldloc.0
  IL_000a:  ret
} // end of method MyClass::MyMethod

Second:

.method public hidebysig instance int32  MyMethod(int32 x) cil managed
{
  // Code size       4 (0x4)
  .maxstack  8
  IL_0000:  ldarg.1
  IL_0001:  ldc.i4.8
  IL_0002:  add
  IL_0003:  ret
} // end of method MyClass::MyMethod

Could someone please shed some light on this? Is there a relevant difference in behaviour of the two implementations (side effects?)? Could the compiler optimize the code, but just doesn't? Thanks!

Dennis
  • 14,210
  • 2
  • 34
  • 54
  • 1
    Your assumption that it doesn't have any side effects is just wrong. It does, rather a big one, the method will not be inlined. Nice demonstrated in [this question](http://stackoverflow.com/questions/8928403/try-catch-speeding-up-my-code). Same story for a for() loop that doesn't do anything. Won't be removed because it *does* have a side-effect, it takes time. The compiler and jitter only optimize the non-obvious cases. – Hans Passant Aug 21 '14 at 09:41
  • `x + 8` can fail under a certain condition. Exercise for the OP: Figure out how that can happen :) – leppie Aug 21 '14 at 10:21

2 Answers2

2

Most optimization is done by the JITter and only some optimization by the compiler generating the IL. So I couldn't tell what actually is compiled to machine code during the run of the program (you could possibly debug though).
You can find information in the post: http://blogs.msdn.com/b/ericlippert/archive/2009/06/11/what-does-the-optimize-switch-do.aspx written by one of the c# compiler team.
There it says:
"The /optimize flag does not change a huge amount of our emitting and generation logic. We try to always generate straightforward, verifiable code and then rely upon the jitter to do the heavy lifting of optimizations when it generates the real machine code"

You can also find information at the link on some cases that are actually optimized for generating IL code.

Update

There is a performance test on the implications in the answer of:
what will empty catch and throw block do?
it confirms that it is not optimized away.

And to the question if the code could be removed there was a discussion at: Can I remove empty catch with throw?

Community
  • 1
  • 1
Uwe Hafner
  • 4,889
  • 2
  • 27
  • 44
  • I can't agree with you here, if even the ReSharper detects this redundancy, the Compiler should do this as well. I can't test it, but when you catch non-cls-compilant Exception and rethrow it, it might get packed into a RunTimeWrappedException after Throw. – Matthias Müller Aug 21 '14 at 08:27
  • I don't say it cannot be detected but possibly the decision by the compiler team was not to optimize it away but leave this optimization to the JITter. If it is optimized at all... – Uwe Hafner Aug 21 '14 at 08:32
  • It should also be remembered that even release builds can be debugged (with a tad more difficulty) and it's not uncommon to use the idiom shown in the question as a handy place to put a breakpoint to inspect an exception. It's not like the setting is called "prevent debugging" so that could be a reason to not optimize it away. – Damien_The_Unbeliever Aug 21 '14 at 09:00
  • But that would then be an argument to not inline methods as well or some more optimizations. I would expect that for release builds the debugging argument should not be a reason not to optimize. – Uwe Hafner Aug 21 '14 at 09:10
  • @Uwe - it can indeed be an argument for not performing those optimizations when a debugger is attached - which is possible because inlining is a runtime decision of the JIT, not a C# compile time decision. And the JIT does make different decisions when a debugger is in fact attached. – Damien_The_Unbeliever Aug 21 '14 at 14:07
  • @Damien_The_Unbeliever - Then I misunderstood you. I was thinking about attaching the debugger after the program was running (and already JIT compiled) as I am used from work. I get the point. – Uwe Hafner Aug 21 '14 at 17:02
0

Compiler can not assume that try/catch block is unnecessary in this case.

If there is any code inside, there is possibility that it will trow an exception. For example:

  • " + " operator can be overwritten in one of the objects and throw an exception...
  • constructor of the object which will be returned from "try" block can throw an exception...

Even if you are not "processing" Exception object which is catched in "catch" statement, many things happens in background. For example constructor of the Exception object is launched (compiler can not be sure if there is or not something important implemented).

As you can see even single (almost empty) try/catch block is not so trivial from compiler point of view.