如果你从catch块中抛出异常,finally什么时候运行?

193
try {
   // Do stuff
}
catch (Exception e) {
   throw;
}
finally {
   // Clean up
}
在上面的代码块中,finally块是在哪个时候被调用?是在抛出异常e之前还是在finally被调用之后再调用catch块?

24
请勿使用"throw e;",因为这会破坏原始异常的堆栈跟踪。你应该只使用"throw;"。或者在抛出新异常之前创建一个新异常,并将InnerException设置为"e"。 - Erv Walter
32
如果finally不是最后一个运行的话,它将成为一个相当糟糕的关键字,你认为呢? - Eric Lippert
@ErvWalter 这还是真的吗?我在VS2017中两种方式都测试了一下,看起来完全一样。您能提供更多信息或参考资料吗?谢谢。 - Jeff Puckett
只是命名建议,使用“Exception ex” - 保留“e”用于事件/委托。 - mr R
8个回答

195

在e被重新抛出后(即在执行catch块之后),它会被调用。

7年后编辑- 一个重要的注意点是,如果e没有被更上层的try/catch块捕获或由全局异常处理程序处理,则finally块可能根本不会执行。


25
永远不要调用Envrionment.FailFast()。 - Johannes Rudolph
28
尝试了Brandon所提供的代码后,我发现如果在外部的try-catch块中从未捕获在前面的catch中抛出的异常,则不会执行finally - Andrew
我记得无论如何 finally 都会被调用,即使已经返回了一个值,他们改变了吗? - Hassan Faghihi
4
在新的文档网站中,微软(Microsoft)谈论了它:https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/try-finally:“在处理异常时,相关的 finally 代码块保证会运行。然而,如果异常未被处理,执行 finally 代码块取决于如何触发异常解除操作,而这又取决于计算机的设置。” - DotNetSparky
2
请注意,“被调用堆栈上方的try/catch块捕获”将包括诸如ASP.NET或测试运行程序中的框架处理程序。更好的表述方式可能是“如果您的程序在catch块之后继续运行,则finally块将执行。” - ArrowCase
显示剩余3条评论

122

不妨试一下:

这是执行下面代码的输出结果:

outer try
inner try
inner catch
inner finally
outer catch
outer finally

使用代码(格式化为垂直空间):

static void Main() {
    try {
        Console.WriteLine("outer try");
        DoIt();
    } catch {
        Console.WriteLine("outer catch");
        // swallow
    } finally {
        Console.WriteLine("outer finally");
    }
}
static void DoIt() {
    try {
        Console.WriteLine("inner try");
        int i = 0;
        Console.WriteLine(12 / i); // oops
    } catch (Exception e) {
        Console.WriteLine("inner catch");
        throw e; // or "throw", or "throw anything"
    } finally {
        Console.WriteLine("inner finally");
    }
}

6
+1,对于如此简单的东西,你应该像Marc一样尝试一下。很棒的是使用嵌套的try/catch/finally进行说明 :) - Allen Rice
3
@AllenRice为什么要尝试去做,如果Marc已经做了,并且我可以通过谷歌搜索找到Marc的答案呢?或者更好的方法是,你自己尝试一下,然后创建一个stackoverflow问题并回答它,以造福他人。 - joshden
12
请注意,如果您在外部 catch 中未捕获异常,则内部 finally 永远不会执行!在这种情况下,输出为 outer try inner try inner catch Unhandled Exception: System.DivideByZeroException... - Andrew
1
@Andrew 你说得对。你可以在这里找到解释:MSDN Magazine 2008年9月:CLR中的未处理异常处理(打开chm文件需要解锁:文件属性->常规->解锁)。如果你用“catch (ArgumentException)”替换外部的catch块,那么没有任何“同意处理异常的异常处理程序”DivideByZeroException,因此也不会执行任何finally块。 - vladimir

51

读完所有回答后,看起来最终的答案是取决于具体情况

  • 如果在catch块中重新抛出异常,并且该异常在另一个catch块内被捕获,则一切都按照文档执行。

  • 但是,如果未处理重新抛出的异常,则finally语句块不会执行。

我在VS2010 w/ C# 4.0中测试了此代码示例。

static void Main()
    {
        Console.WriteLine("Example 1: re-throw inside of another try block:");

        try
        {
            Console.WriteLine("--outer try");
            try
            {
                Console.WriteLine("----inner try");
                throw new Exception();
            }
            catch
            {
                Console.WriteLine("----inner catch");
                throw;
            }
            finally
            {
                Console.WriteLine("----inner finally");
            }
        }
        catch
        {
            Console.WriteLine("--outer catch");
            // swallow
        }
        finally
        {
            Console.WriteLine("--outer finally");
        }
        Console.WriteLine("Huzzah!");

        Console.WriteLine();
        Console.WriteLine("Example 2: re-throw outside of another try block:");
        try
        {
            Console.WriteLine("--try");
            throw new Exception();
        }
        catch
        {
            Console.WriteLine("--catch");
            throw;
        }
        finally
        {
            Console.WriteLine("--finally");
        }

        Console.ReadLine();
    }

这是输出结果:

示例1:在另一个try块中重新抛出:
--外部try块
----内部try块
----内部catch块
----内部finally块
--外部catch块
--外部finally块
恭喜!

示例2:在另一个try块外重新抛出:
--try块
--catch块

未处理的异常:System.Exception: 引发了类型为'System.Exception'的异常。
在C:\local source\ConsoleApplication1\Program.cs的ConsoleApplication1.Program.Main()第53行。


8
太棒了,我之前并不知道! - Andrew
1
请注意,最后的finally可能会运行,这取决于您的选择:https://stackoverflow.com/a/46267841/480982 - Thomas Weller
3
有趣的是,在.NET Core 2.0上,finally部分会在未处理的异常之后运行。 - Mahdi Ghiasi
有趣的是,我刚在.NET Core 2.0和.NET Framework 4.6.1中进行了测试,它们都在未处理的异常后运行finally。这种行为改变了吗? - Cameron Bielstein
我刚在 .Net 5.0 中运行了你的测试,结果如下:示例2:在另一个 try 块之外重新抛出异常: --try --catch 未处理的异常。System.Exception: 引发了类型为 'System.Exception' 的异常。 在 FinallyTest.Program.Main() 位置 \Program.cs:line 36 --finallyfinally 块在第二个测试中运行。 - Gargamel

24

您的示例代码与以下代码的行为完全相同:

try {
    try {
        // Do stuff
    } catch(Exception e) {
        throw e;
    }
} finally {
    // Clean up
}

顺便提一句,如果您确实想要使用throw e;(也就是抛出您刚捕获的同一个异常),那么最好只使用throw;,因为这样会保留原始的堆栈跟踪而不会创建一个新的。


我认为这不正确。最后应该在外部try块内而不是外部。 - Matthew Pigram
@MatthewPigram:你的意思是什么?实际上,即使捕获块重新抛出异常,finally块也会在catch块之后运行,这正是我的代码片段要说明的。 - Daniel Pryden
从我的理解来看,他试图在另一个 try 块中进行 try-catch-finally,而不是 try-catch 中再嵌套一个 try-catch-finally。 - Matthew Pigram
1
@MatthewPigram:我的答案根本没有任何“try-catch-finally”结构。它有一个“try-finally”,在那个try块内,我的答案有一个“try-catch”。我试图通过使用两个2部分结构来解释3部分结构的行为。我没有看到原始问题中有第二个try块的任何迹象,所以我不明白你从哪里得到了那个。 - Daniel Pryden

14

如果在 catch 处理程序块内发生未处理的异常,则 finally 块将完全不会被调用。

  static void Main(string[] args)
  {
     try
     {
        Console.WriteLine("in the try");
        int d = 0;
        int k = 0 / d;
     }
     catch (Exception e)
     {
        Console.WriteLine("in the catch");
        throw;
     }
     finally
     {
        Console.WriteLine("In the finally");
     }
  }

输出:

C:\users\administrator\documents\TestExceptionNesting\bin\Release>TestExceptionNesting.exe

在 try 中

在 catch 中

未处理的异常: System.DivideByZeroException: 尝试除以零。 在 C:\users\administrator\documents\TestExceptionNesting\TestExceptionNesting.cs 的 TestExceptionNesting.Program.Main(String[] args) 第22行

C:\users\administrator\documents\TestExceptionNesting\bin\release>

今天在面试中被问到这个问题,面试官反复问我“你确定 finally 没有被调用吗?” 我不确定这是否是一个诡计问题,还是面试官有其他想法并且写了错误的代码让我调试。为了安心,我回家尝试了一下(编译和运行,没有调试交互)。


除非抛出的异常在堆栈中更高层次的其他catch中被捕获,否则它可能会执行,如果该抛出的异常被处理...或者我可能是错的... - tomosius
@tomosius,是的,这就是Brandon的答案所解释的。 :) - Andrew
@tomosius 这就是为什么我开始时指定了“如果有未处理的异常”。如果抛出的异常在某个地方被捕获,那么根据定义,我们正在讨论不同的情况。 - Eusebio Rufian-Zilbermann
1
这不是真的。至少对于.NET Core 3.1来说不是。一个简单的新控制台项目,使用这段代码会在异常后显示“在finally中”。 - empz
有趣的是行为已经改变了,当我发布时 .NET Core 甚至还不存在 ;) - Eusebio Rufian-Zilbermann

2
一种简单的方法是调试您的代码并注意何时调用finally。

1
你可以做到,我已经完成了这个以确保清理。
using System;
class Program
{
static void Main()
{
    FinallyThrow();
    Console.ReadLine();
}
static void FinallyThrow()
{
    // Get finally block to execute EVEN IF there is an unhandled exception
    Exception thrownEx = null;
    try
    {
        Console.WriteLine("try..");
        throw new InvalidOperationException();
    }
    catch (Exception ex)
    {
        Console.WriteLine("catch..");
        thrownEx = ex;
    }
    finally
    {
        Console.WriteLine("finally..");
        if (thrownEx != null) throw thrownEx;
    }
}
}

输出(在未处理异常后)

try..
catch..
finally..

.即使出现未处理的异常也要一直运行


2
你将会得到一个新的堆栈跟踪;相反,抛出一个带有thrownEx作为InnerException的新异常以保留内部堆栈跟踪。 - Spoc

1

使用C#控制台应用程序进行测试,finally代码在抛出异常后被执行:存在“应用程序错误对话框”,并在选择“关闭程序”选项后,在控制台窗口中执行了finally块。 但是,在finally代码块内设置断点后,我永远无法触发它。调试器一直停在throw语句处。 以下是我的测试代码:

    class Program
    {
       static void Main(string[] args)
       {
          string msg;
          Console.WriteLine(string.Format("GetRandomNuber returned: {0}{1}", GetRandomNumber(out msg), msg) == "" ? "" : "An error has occurred: " + msg);
       }

       static int GetRandomNumber(out string errorMessage)
       {
         int result = 0;
         try
         {
            errorMessage = "";
            int test = 0;
            result = 3/test;
            return result;
         }
         catch (Exception ex)
         {
            errorMessage = ex.Message;
            throw ex;

         }
         finally
         {
            Console.WriteLine("finally block!");
         }

       }
    }

在VS2010中调试 - .NET Framework 4.0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接