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

C#.NetException Handling

C# Problem Overview


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?

C# Solutions


Solution 1 - C#

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

Solution 2 - C#

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;
}

Solution 3 - C#

The finally clause executes after the return statement but before actually returning from the function. It has little to do with thread safety, I think. It is not a hack - the finally is guaranteed to always run no matter what you do in your try block or your catch block.

Solution 4 - C#

Adding onto the answers given by Marc Gravell and Jon Skeet, it is important to note objects and other reference types behave similarly when returned but do have some differences.

The "What" that gets returned follows the same logic as simple types:

class Test {
    public static Exception AnException() {
        Exception ex = new Exception("Me");
        try {
            return ex;
        } finally {
            // Reference unchanged, Local variable changed
            ex = new Exception("Not Me");
        }
    }
}

The reference that is being returned has already been evaluated before the local variable is assigned a new reference in the finally block.

The execution is essentially:

class Test {
    public static Exception AnException() {
        Exception ex = new Exception("Me");
        Exception CS$1$0000 = null;
        try {
            CS$1$0000 = ex;
        } finally {
            // Reference unchanged, Local variable changed
            ex = new Exception("Not Me");
        }
        return CS$1$0000;
    }
}

The difference is it would still be possible to modify mutable types using the properties/methods of the object which can result in unexpected behaviors if you are not careful.

class Test2 {
    public static System.IO.MemoryStream BadStream(byte[] buffer) {
        System.IO.MemoryStream ms = new System.IO.MemoryStream(buffer);
        try {
            return ms;
        } finally {
            // Reference unchanged, Referenced Object changed
            ms.Dispose();
        }
    }
}

A second thing to consider about try-return-finally is that parameters passed "by reference" can still be modified after the return. Only the return value has been evaluated and is stored in a temporary variable waiting to be returned, any other variables are still modified the normal way. The contract of an out parameter can even go unfulfilled until the finally block this way.

class ByRefTests {
    public static int One(out int i) {
        try {
            i = 1;
            return i;
        } finally {
            // Return value unchanged, Store new value referenced variable
            i = 1000;
        }
    }

    public static int Two(ref int i) {
        try {
            i = 2;
            return i;
        } finally {
            // Return value unchanged, Store new value referenced variable
            i = 2000;
        }
    }

    public static int Three(out int i) {
        try {
            return 3;
        } finally {
            // This is not a compile error!
            // Return value unchanged, Store new value referenced variable
            i = 3000;
        }
    }
}

Like any other flow construct "try-return-finally" has its place and can allow for cleaner looking code than writing the structure it actually compiles to. But it must be used carefully to avoid gotcha's.

Solution 5 - C#

If x is a local variable, I don't see the point, as x will be effectively set to null anyway when the method is exited and the value of the return value is not null (since it was placed in the register before the call to set x to null).

I can only see doing this happening if you want to guarantee the change of the value of a field upon return (and after the return value is determined).

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionDmitri NesterukView Question on Stackoverflow
Solution 1 - C#Jon SkeetView Answer on Stackoverflow
Solution 2 - C#Marc GravellView Answer on Stackoverflow
Solution 3 - C#Otávio DécioView Answer on Stackoverflow
Solution 4 - C#Arkaine55View Answer on Stackoverflow
Solution 5 - C#casperOneView Answer on Stackoverflow