尝试实际发生的情况{return x; }最后{x = null; }语句?


259

我在另一个问题中看到了这个技巧,并且想知道是否有人可以向我解释这在世界上是如何工作的?

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

我的意思是,该finally条款真正执行return声明?此代码的线程不安全性如何?您能想到可以通过此骇客完成的任何其他骇客try-finally吗?

Answers:


235

否-在IL级别,您不能从异常处理块内部返回。它本质上将其存储在变量中,然后返回

即类似于:

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

例如(使用反射器):

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

编译为:

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

这基本上声明了一个局部变量(CS$1$0000),将值放入变量中(在已处理的块内),然后在退出该块后加载该变量,然后将其返回。Reflector将此呈现为:

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

10
这不是ocdedio所说的吗:finally是在计算返回值之后并且真正从函数返回之前执行的???
mmmmmmmm

我认为“异常处理块”与异常和异常处理无关。这是关于.NET如何实现最终资源保护构造的。
g.pickardou 2015年

361

执行了finally语句,但是返回值不受影响。执行顺序为:

  1. 返回语句执行前的代码
  2. 返回语句中的表达式被求值
  3. 最终执行块
  4. 返回步骤2中评估的结果

这是一个简短的程序来演示:

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

这将打印“ try”(因为这就是返回的内容),然后打印“ finally”,因为这是x的新值。

当然,如果我们返回对可变对象(例如StringBuilder)的引用,则在final块中对该对象所做的任何更改在返回时都将可见-这并没有影响返回值本身(这只是一个参考)。


我想问一下,有没有在Visual Studio中的任何选项来查看编写C#代码生成的中间语言(IL)在执行的时候....
谜州

例外情况是“如果我们返回对可变对象(例如StringBuilder)的引用,则对finally块中的对象所做的任何更改都将在返回时可见”,如果StringBuilder对象在finally块中设置为null,在这种情况下,将返回非null对象。
尼克

4
@Nick:这不是对对象的更改,而是对变量的更改。根本不引用变量的先前值。所以不,这也不例外。
乔恩·斯基特

3
@Skeet是否表示“ return语句返回副本”?
prabhakaran 2014年

4
@prabhakaran:好吧,它在语句的那一刻对表达式求return值,并将返回该值。由于控件离开该方法,因此评估该表达式。
乔恩·斯基特

19

finally子句在return语句之后但实际上从函数返回之前执行。我认为,这与线程安全无关。这不是骇客-无论您在try块还是catch块中做什么,finally都将始终运行。


13

除了Marc Gravell和Jon Skeet给出的答案外,重要的是要注意对象和其他引用类型在返回时的行为类似,但有一些区别。

返回的“ What”遵循与简单类型相同的逻辑:

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

在为finally块中的局部变量分配新引用之前,已经对返回的引用进行了评估。

执行本质上是:

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

区别在于仍然可以使用对象的属性/方法来修改可变类型,如果不小心,可能会导致意外行为。

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

关于try-return-finally的第二件事要考虑的是,在返回之后仍可以修改“按引用”传递的参数。仅对返回值进行了评估,并将其存储在等待返回的临时变量中,其他所有变量仍按常规方式修改。直到最终阻塞这种方式,out参数的约定才能执行。

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”也有它的位置,并且比起编写实际编译的结构,它可以使代码看起来更简洁。但是,必须小心使用它,以免引起混乱。


4

如果x是局部变量,我看不到要点,因为x无论何时退出方法并且返回值的值都不为null时,无论如何都会将其有效地设置为null(因为在调用set之前将其放置在寄存器中x为null)。

如果您想保证返回时(以及确定返回值之后)字段值的更改,我只会看到发生这种情况。


除非代表也捕获了局部变量:)
乔恩·斯凯特

然后有一个闭包,并且该对象不能被垃圾回收,因为仍然有一个引用。
递归

但是我仍然不明白为什么除非要使用其值,否则为什么要在委托中使用局部变量。
递归
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.