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!

2
  • 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. 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. Commented Aug 21, 2014 at 9:41
  • x + 8 can fail under a certain condition. Exercise for the OP: Figure out how that can happen :) Commented Aug 21, 2014 at 10:21

2 Answers 2

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?

Sign up to request clarification or add additional context in comments.

6 Comments

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.