关于C#:如果我在try块中返回一个值,finally语句中的代码会触发吗?

Will code in a Finally statement fire if I return a value in a Try block?

我正在为一个朋友检查一些代码,并说他在tryfinally块内使用了一个返回语句。即使try块的其余部分没有,finally部分中的代码是否仍会触发?

例子:

1
2
3
4
5
6
7
8
9
10
11
12
public bool someMethod()
{
  try
  {
    return true;
    throw new Exception("test"); // doesn't seem to get executed
  }
  finally
  {
    //code in question
  }
}


简单回答:是的。


通常,是的。最后一节保证执行发生的任何操作,包括异常或返回语句。此规则的异常是线程上发生的异步异常(OutOfMemoryExceptionStackOverflowException)。

要进一步了解异步异常和这种情况下的可靠代码,请阅读有关受约束执行区域的内容。


下面是一个小测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Class1
{
    [STAThread]
    static void Main(string[] args)
    {
        Console.WriteLine("before");
        Console.WriteLine(test());
        Console.WriteLine("after");
    }

    static string test()
    {
        try
        {
            return"return";
        }
        finally
        {
            Console.WriteLine("finally");
        }
    }
}

结果是:

1
2
3
4
before
finally
return
after


从msdn报价

finally is used to guarantee a statement block of code executes regardless of how the preceding try block is exited.


一般来说是的,最后会运行。

对于以下三种情况,最终将始终运行:

  • 未发生异常
  • 同步异常(正常程序流中发生的异常)。这包括从System.Exception派生的符合CLS的异常以及不从System.Exception派生的不符合CLS的异常。不符合CLS的异常由RuntimeWrappedException自动包装。C不能抛出非CLS投诉异常,但C++等语言可以。C可以调用用一种语言编写的代码,这种语言可以抛出不符合CLS的异常。
  • 异步线程接收从.NET 2.0开始,ThreadAbortException将不再阻止Finally运行。线程异常现在被提升到最后的前后。只要在线程中止之前实际输入了try,finally将始终运行,并且不会被线程中止中断。
  • 以下场景中,最终将不运行:

    异步StackOverflowException。从.NET 2.0开始,堆栈溢出将导致进程终止。除非应用了进一步的约束以使finally成为CER(受约束的执行区域),否则finally将不会运行。一般用户代码中不应使用CER。只有在清理代码始终运行是非常关键的情况下才应该使用它们——在所有进程都在堆栈溢出时关闭之后,因此默认情况下将清理所有托管对象。因此,CER应该与之相关的唯一地方是分配给进程外部的资源,例如非托管句柄。

    通常,非托管代码在被用户代码使用之前由某个托管类包装。托管包装类通常使用SafeHandle包装非托管句柄。SafeHandle实现了一个关键的终结器和一个在CER中运行的发布方法,以确保清理代码的执行。出于这个原因,你不应该看到CER在用户代码中乱丢东西。

    因此,finally不在stackOverflowException上运行的事实应该不会对用户代码产生任何影响,因为进程无论如何都会终止。如果您有一些边缘情况需要清理一些非托管资源(在SafeHandle或CriticalFinalizerObject之外),那么请按如下方式使用CER;但请注意,这是一种糟糕的做法--非托管概念应通过设计抽象为托管类和适当的SafeHandle。

    例如。,

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // No code can appear after this line, before the try
    RuntimeHelpers.PrepareConstrainedRegions();
    try
    {
        // This is *NOT* a CER
    }
    finally
    {
        // This is a CER; guaranteed to run, if the try was entered,
        // even if a StackOverflowException occurs.
    }


    有一个非常重要的例外,我在其他答案中没有提到,而且(在用C语言编程18年后)我不能相信我不知道。

    如果你在你的catch块(不仅仅是奇怪的StackOverflowExceptions块和类似的块)中抛出或触发任何类型的异常,而你没有在另一个try/catch块中使用整个try/catch/finally块,那么你的finally块就不会执行。这很容易被证明——如果我自己没有看到它,考虑到我经常读到它只是一个很奇怪的,很小的角落案件,可以导致一个finally块不执行,我不会相信它。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    static void Main(string[] args)
    {
        Console.WriteLine("Beginning demo of how finally clause doesn't get executed");
        try
        {
            Console.WriteLine("Inside try but before exception.");
            throw new Exception("Exception #1");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Inside catch for the exception '{ex.Message}' (before throwing another exception).");
            throw;
        }
        finally
        {
            Console.WriteLine("This never gets executed, and that seems very, very wrong.");
        }

        Console.WriteLine("This never gets executed, but I wasn't expecting it to.");
        Console.ReadLine();
    }

    我相信这是有原因的,但奇怪的是,它并没有被更广泛地知道。(例如,这里已经提到,但在这个特定问题中没有提到。)


    我意识到我迟到了,但在引发异常的场景中(不同于操作示例),msdn状态(https://msdn.microsoft.com/en-us/library/zwc8s4fz.aspx):"如果未捕获异常,则最终块的执行取决于操作系统是否选择触发异常释放操作。"

    只有当调用堆栈上的某个其他函数(如main)捕获异常时,才保证执行finally块。这个细节通常不是问题,因为所有的运行时环境(clr和os)c程序都是在进程退出时拥有的大多数空闲资源(文件句柄等)上运行的。在某些情况下,它可能是至关重要的:一个数据库操作正在进行中,您希望提交resp。或者一些远程连接,这些连接可能不会被操作系统自动关闭,然后阻塞服务器。


    对。事实上,这就是最后陈述的要点。除非发生了某种类型的情况(内存不足、计算机未插电等),否则应始终执行finally语句。


    它也不会触发未捕获的异常并在Windows服务中承载的线程中运行。

    在Windows服务中运行的线程中不执行finally


    如果您退出应用程序时使用系统出口(0);如

    1
    2
    3
    4
    5
    6
    7
    8
    9
    try
    {
        System.out.println("try");
        System.exit(0);
    }
    finally
    {
       System.out.println("finally");
    }

    结果是:尝试


    是的,最后打电话。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
      public static bool someMethod()
        {
            try
            {

                return true;
                throw new Exception("test"); // doesn't seem to get executed
            }
            finally
            {
                //code in question
            }
        }


    finally块的主要目的是执行其中写入的内容。它不应该依赖于Try或Catch中发生的任何事情。但是,对于System.Environment.Exit(1),应用程序将退出,而不移动到下一行代码。


    99%的场景可以保证finally块内的代码可以运行,但是,考虑到这个场景:您有一个线程具有try->finally块(没有catch),并且在该线程内得到一个未处理的异常。在这种情况下,线程将退出,它的finally块将不被执行(在这种情况下,应用程序可以继续运行)

    这种情况非常罕见,但这只是为了表明答案并不总是"是",大多数情况下是"是",有时在罕见情况下是"否"。