Coder Perfect

In a try return x; finally x = null; sentence, what exactly happens?

Problem

I read this suggestion in another inquiry and was hoping someone might clarify how it works.

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

Do the finally clause and the return statement actually execute after each other? Is this code thread-unsafe? Is there anything else you can do with this try-finally hack that you can think of?

Asked by Dmitri Nesteruk

Solution #1

The finally statement is run, but no change is made to the return value. The following is the sequence of execution:

Here’s a quick video 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 outputs “try” (because that’s what’s returned), followed by “finally,” which is the updated value of x.

Of course, if we’re returning a reference to a mutable object (such as a StringBuilder), any modifications made to the object in the finally block would be visible on return – but this hasn’t changed the return value (which is just a reference).

Answered by Jon Skeet

Solution #2

No, you can’t return from an exception-handled block at the IL level. It just saves it in a variable and then returns it.

i.e. similar to:

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

Consider the following scenario (using a 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), stores the value in the variable (inside the handled block), then loads and returns the variable after departing the block. This is how Reflector renders it:

private static int Test()
{
    int CS$1$0000;
    try
    {
        CS$1$0000 = SomeNumber();
    }
    finally
    {
        Foo();
    }
    return CS$1$0000;
}

Answered by Marc Gravell

Solution #3

The finally clause runs after the return statement but before the function is actually exited. I don’t believe it has anything to do with thread safety. It’s not a trick; no matter what you do in your try or catch blocks, the finally is guaranteed to run.

Answered by Otávio Décio

Solution #4

In addition to Marc Gravell’s and Jon Skeet’s responses, it’s worth noting that objects and other reference types behave similarly when returned, but there are some differences.

The returned “What” 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");
        }
    }
}

Before the local variable is allocated a new reference in the finally block, the reference that is being returned has already been evaluated.

Basically, here’s how it’s done:

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 that you can still edit mutable types using the object’s properties/methods, which can lead to unexpected behavior if you’re 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();
        }
    }
}

Another aspect of try-return-finally to consider is that arguments given “by reference” might still be changed after the return. Only the return value has been evaluated and is put in a temporary variable that will be returned; all other variables remain unchanged. An out parameter’s contract can even go unfulfilled until the last block is reached in this manner.

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

“Try-return-finally,” like any other flow construct, has its place and can result in cleaner-looking code than writing the structure it really compiles to. However, it must be utilized with caution to avoid getting caught.

Answered by Arkaine55

Solution #5

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

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

Answered by casperOne

Post is based on https://stackoverflow.com/questions/421797/what-really-happens-in-a-try-return-x-finally-x-null-statement