What really happens in a try { return x; } finally { x = null; } statement?


Question

I saw this tip in another question and was wondering if someone could explain to me how on earth this works?

try { return x; } finally { x = null; }

I mean, does the finally clause really execute after the return statement? How thread-unsafe is this code? Can you think of any additional hackery that can be done w.r.t. this try-finally hack?

1
246
1/23/2014 10:54:08 AM

Accepted Answer

No - at the IL level you can't return from inside an exception-handled block. It essentially stores it in a variable and returns afterwards

i.e. similar to:

int tmp;
try {
  tmp = ...
} finally {
  ...
}
return tmp;

for example (using reflector):

static int Test() {
    try {
        return SomeNumber();
    } finally {
        Foo();
    }
}

compiles to:

.method private hidebysig static int32 Test() cil managed
{
    .maxstack 1
    .locals init (
        [0] int32 CS$1$0000)
    L_0000: call int32 Program::SomeNumber()
    L_0005: stloc.0 
    L_0006: leave.s L_000e
    L_0008: call void Program::Foo()
    L_000d: endfinally 
    L_000e: ldloc.0 
    L_000f: ret 
    .try L_0000 to L_0008 finally handler L_0008 to L_000e
}

This basically declares a local variable (CS$1$0000), places the value into the variable (inside the handled block), then after exiting the block loads the variable, then returns it. Reflector renders this as:

private static int Test()
{
    int CS$1$0000;
    try
    {
        CS$1$0000 = SomeNumber();
    }
    finally
    {
        Foo();
    }
    return CS$1$0000;
}
229
1/7/2009 7:50:20 PM

The finally statement is executed, but the return value isn't affected. The execution order is:

  1. Code before return statement is executed
  2. Expression in return statement is evaluated
  3. finally block is executed
  4. Result evaluated in step 2 is returned

Here's a short program to demonstrate:

using System;

class Test
{
    static string x;

    static void Main()
    {
        Console.WriteLine(Method());
        Console.WriteLine(x);
    }

    static string Method()
    {
        try
        {
            x = "try";
            return x;
        }
        finally
        {
            x = "finally";
        }
    }
}

This prints "try" (because that's what's returned) and then "finally" because that's the new value of x.

Of course, if we're returning a reference to a mutable object (e.g. a StringBuilder) then any changes made to the object in the finally block will be visible on return - this hasn't affected the return value itself (which is just a reference).


Licensed under: CC-BY-SA with attribution
Not affiliated with: Stack Overflow
Icon