22

I have a simple test code that works as expected in .NET3.5, but the same code behaves completely different on a project created with .NET4.5.1.

class Program
{
    static void Main(string[] args)
    {
        try
        {
            string a = null;
            var x = a.Length;
        }
        catch (Exception ex)
        {
            throw;
        }
        finally
        {
            Console.WriteLine("This is the finally block.");
        }
        Console.WriteLine("You should not be here if an exception occured!");
    }
}

First of all, the weird thing is that the NullReferenceException type exception is completely ignored in .NET4.5.1 when running the compiled RELEASE exe file. Basically, no error is thrown, although in debug mode the exception is thrown.

Second of all (and most importantly), if the error is different than NullReferenceException, such as “index out of range” for example, then the exception is actually thrown as expected, but the “finally” block is never hit which is not the behavior I expected from the try-catch-finally block. I tried in different machines, and had 2 other colleagues of mine also trying and we all got the same result.

It seems that either I never really understood the try-catch-finally block, or .NET4.5.1 handles exception in a different way, or there is some bug with .NET4.5.1. All I know is that the code above works in .NET3.5 as I expected it to work, but I don’t seem to get the same result when running it in .NET4.5.1 .

Can someone shed some light on this? I am at a total loss right now.

EDIT Based on Eric J answer I was able to resolve the NullReferenceException issue. Since I asked 2 questions, I'll create a new thread for the second question. Try-Catch-Finally block problems with .NET4.5.1

Community
  • 1
  • 1
MarkJ
  • 353
  • 1
  • 9

1 Answers1

22
string a = null;
var x = a.Length;

In RELEASE mode, the jitter (just in time compiler) is able to prove that x is never referenced, so is able to remove the assignment.

In DEBUG mode, the jitter does not perform that optimization.

To force the exception to be thrown, do something with x (e.g. as @Henk suggests in the comments, WriteLine(x)).

EDIT

Eric Lippert noted in the comments

...I am as surprised as anyone that the jitter would elide a string length instruction that could throw. That seems wrong to me...

The jitter optimization may be overly-aggressive.

Eric J.
  • 147,927
  • 63
  • 340
  • 553
  • You should probably add that `x` is never referenced **and** the Length property accessor is a *pure method*. The compiler could not perform the optimization otherwise. – Michael Petito Jun 16 '15 at 23:26
  • 2
    Wait, so the compiler considers throwing exceptions *not* a side effect (which it clearly is, I mean the given code sequence demonstrates this)? There either has to be some additional requirements (imagine the consequences otherwise!) or this is a bug. – Voo Jun 17 '15 at 07:33
  • @MichaelPetito What does it means that it is a *pure method*? I see that it is a *MethodImplOptions.InternalCall*, and it is probably for that reason that it can be optimized, but still it isn't clear... – xanatos Jun 17 '15 at 07:33
  • @xanatos a method is *pure* if it has no side effects – germi Jun 17 '15 at 08:49
  • @germi Ok... He meant that definition of `pure`... but still it isn't enough.... a `a.Trim()` wouldn't have any side-effect but would throw. – xanatos Jun 17 '15 at 08:50
  • @Voo: Exeption handling is just that, a means of handling exceptional situations in your program. It should not be used as a means of control flow. The optimizer is taking that position when determining that `x` is never referenced and therefore the assignment can be optimized away. – Eric J. Jun 17 '15 at 18:33
  • Following up on @MichaelPetito's comment, the reason is that the .Length must be non-virtual is that the implementation for a virtual property or method could be provided by some library not yet written. The compiler has no way of knowing whether that property/method produces side effects. For a great overview of this issue, see Eric Lippert's answer here http://stackoverflow.com/q/30816496/141172 – Eric J. Jun 17 '15 at 18:39
  • Following up on your comment on another question: first off, to clarify, by "the compiler" you mean the jitter, not the C# compiler, correct? The C# compiler should generate the string length instruction regardless of whether the local is ever used or not. Second, I am as surprised as anyone that the jitter would elide a string length instruction that could throw. That seems wrong to me. Unfortunately my go-to guy for questions about the jitter is no longer at Microsoft and I don't have contact info. I'll ask around and see if I can find out what's happening here. – Eric Lippert Jun 17 '15 at 19:16
  • And to clarify my previous comment, I was misremembering; the C# compiler generates a ldlen for an *array* length, not a *string* length. But my point remains; I am surprised that this operation would be elided since it can throw. – Eric Lippert Jun 17 '15 at 19:21
  • @EricJ. There's a very clear rule for pretty much every programming language that an optimization is *not* allowed to change the observable behavior of a single-threaded piece of code. *Not* throwing an exception is definitely a change in behavior and should not be allowed as far as I can see. Optimizing compilers are complicated, JITs doubly so, bugs happen easy enough, but this is certainly not the correct interpretation of this valid C# program. – Voo Jun 18 '15 at 06:24