3

I don't really understand how the instructions flow in the following code. The body of finally is guaranteed to be executed before the method returns. If so, the return value should be 0 rather than 1.

Could you explain the internal mechanism why the return value is still 1 even though the finally has reset it to 0?

class Container
{
    int data = 0;
    public int Retrieve()
    {
        try
        {
            Inc();
            return data;
        }
        finally
        {
            Reset();
            //return data;
        }
    }
    void Reset()
    {
        data = 0;
        WriteLine("Reset");
    }
    void Inc() => data++;
}

class ReturnInTry
{
    static void Main()
    {
        Clear();
        WriteLine("Start");
        WriteLine(new Container().Retrieve());
        WriteLine("End");
    }
}
Boann
  • 48,794
  • 16
  • 117
  • 146
Second Person Shooter
  • 14,188
  • 21
  • 90
  • 165
  • 1
    your `data` is an `int`, which is a value type. Thus when you do `return data;` the assignment of `data` in the `finally` block does not affect the value from the return anymore. – derpirscher Jun 06 '21 at 11:51
  • 1
    See [this](https://stackoverflow.com/questions/3715198/will-finally-blocks-be-executed-if-returning-from-try-or-catch-blocks-in-c-if) it is same as your question. – Alireza Ahmadi Jun 06 '21 at 12:13

1 Answers1

4

Because when the return instruction is executed, it PUSHs in the CPU Stack the value to be returned.

Then the finally block is executed but it does not modify the value already pushed.

Thus after the method PROC RET, the caller POPs the value and has what it has been PUSHed, but the data itself has been reseted.

Therefore, calling the method again will return 0.

This means that the return statement is executed first, and the code in finally is executed after, so the result is previously stored and changing data does not change this stored in the stack result.

try-finally (C# Reference)

We can check that using for example ILSpy:

.method public hidebysig 
  instance int32 Retrieve () cil managed 
{
  // Method begins at RVA 0x4cf4
  // Code size 30 (0x1e)
  .maxstack 1
  .locals init ( [0] int32 )

  .try
  {
    // Inc();
    IL_0002: ldarg.0
    IL_0003: call instance void ConsoleApp.Container::Inc()

    // return data;
    IL_0009: ldarg.0
    IL_000a: ldfld int32 ConsoleApp.Container::data
    IL_000f: stloc.0

    IL_0010: leave.s IL_001c
  } // end .try
  finally
  {
    // Reset();
    IL_0013: ldarg.0
    IL_0014: call instance void ConsoleApp.Container::Reset()
    // }
    IL_001b: endfinally
  } // end handler

  IL_001c: ldloc.0
  IL_001d: ret
} // end of method Container::Retrieve

OpCodes.Stloc_0 Field

OpCodes.ldloc_0 Field

// Console.WriteLine(new Container().Retrieve());
IL_000c: newobj instance void ConsoleApp.Container::.ctor()
IL_0011: call instance int32 ConsoleApp.Container::Retrieve()
IL_0016: call void [mscorlib]System.Console::WriteLine(int32)

OpCodes.Call Field

  • 3
    To simplify the whole everything in words that can be understood by anyone, I would say that, if you put breakpoints in that code, you will notice that the return statement is reached first and then the finally block, which makes sense. This simply means that `finally` is actually executed after the `return`. – Transcendent Jun 06 '21 at 12:01
  • Thank you very much. I have an old question related to this [here](https://stackoverflow.com/q/58002630/5482465). You might provide me with a much better answer there. – Second Person Shooter Jun 06 '21 at 12:20
  • @TheShortestMustacheTheorem I haven't studied and really used async yet due to lack of time, sorry. –  Jun 06 '21 at 12:24