【问题标题】:Why does returning a variable in a try block not change the value of the returned thing when this variable is reset in the finally section? [duplicate]为什么在 try 块中返回变量不会更改在 finally 部分中重置该变量时返回的值? [复制]
【发布时间】:2021-06-06 11:47:52
【问题描述】:

我不太明白以下代码中的指令是如何流动的。 finally 的主体保证在方法返回之前执行。如果是这样,返回值应该是 0 而不是 1。

您能否解释一下为什么即使finally 已将其重置为 0,返回值仍为 1 的内部机制?

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

【问题讨论】:

  • 你的data 是一个int,它是一个值类型。因此,当您执行return data; 时,finally 块中的data 分配不再影响返回值。
  • this这和你的问题一样。

标签: c# methods return-value try-finally


【解决方案1】:

因为return指令在执行时,会将要返回的值压入CPU Stack。

然后finally块被执行,但它不会修改已经推送的值。

因此,在 PROC RET 方法之后,调用者将值 POP 出来并拥有它已被推送的内容,但数据本身已被重置。

因此,再次调用该方法将返回 0。

这意味着先执行return语句,然后执行finally中的代码,所以结果是先前存储的,更改data不会改变这个存储在堆栈中的结果。

try-finally (C# Reference)

我们可以使用 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

【讨论】:

  • 为了用任何人都可以理解的语言来简化所有内容,我想说的是,如果您在该代码中放置断点,您会注意到首先到达 return 语句,然后到达 finally 块,这是有道理的。这仅仅意味着finally实际上是在return之后执行的。
  • 非常感谢。我有一个与此here 相关的老问题。您可能会在那里为我提供更好的答案。
  • @TheShortestMustacheTheorem 由于时间不够,我还没有研究并真正使用过异步,抱歉。
猜你喜欢
  • 2013-04-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-07-20
  • 2016-02-01
  • 2011-04-19
相关资源
最近更新 更多