C# 异常处理 finally 在 catch 块之前阻塞

C# exception handling finally block before catch block

我对 C# 中抛出异常的方式感到很困惑。如果发生异常,在try块中, 1.it 被扔到 catch 块, 2. 当且仅当 catch 块捕获到它时,finally 块才会被执行。 3. finally 块最后执行,前提是 catch 语句捕获了它。

但是,当我尝试 运行 下面的程序时,输出的是 A,B 而不是 BA.Is 我的理解有问题吗?谢谢。

class Program
 {
     public static void Main(string[] args)
     {
         try
         {
             int a = 2;
             int b = 10 / a;
             try
             {
                 if (a == 1)
                     a = a / a - a;
                 if (a == 2)
                 {
                     int[] c = { 1 };
                     c[8] = 9;
                 }
             }
             finally
             {
                 Console.WriteLine("A");
             }
        }
        catch (IndexOutOfRangeException e)
        {
             Console.WriteLine("B");
        }
        Console.ReadLine();
    }
 }

异常发生在a==2,我知道外层的catch会捕获到这个异常。然而,finally是先被执行的吗?显示此内容的原因是什么?

已编辑

从 C# 文档我们知道,无论是否发生异常,Finally 块都会被执行。

但是,我的 finally 块永远不会执行,在 return 中,我收到 运行 时间错误

class Program
{
    public static void Main(string[] args)
    {
        try
        {
            int a = 2;
            int b = 10 / a;
            try
            {
                if (a == 1)
                    a = a / a - a;
                if (a == 2)
                {
                    int[] c = { 1 };
                    c[8] = 9;
                }
            }
            finally
            {
                Console.WriteLine("A");
            }
        }

        finally{
            Console.WriteLine("finally");
        }
        
        Console.ReadLine();
    }
}

如果您只有一个 try-catch-finally 块,那么 catch 确实先于 finally。但是在这里,try-finally 和 try-catch 块按照从内到外的顺序 运行。因此最后 运行 是第一个。

finally 在控制离开 try 块时执行 出于任何原因;不仅仅是在同一级别有一个 catch 块 - 那将是一个 fault 处理程序(在 CLR 术语中),我认为我们在 C# 中仍然无法做到。

因此它进入打印 Afinally,因为控制正在离开那个 try 块。它离开它是因为在外部 catch 块中捕获了一个异常。但是,这不会改变任何 timings/orderings。


请注意,如果异常在您的代码中的任何地方都完全未被捕获,则可能会出现一些奇怪的情况。异常处理分两个阶段进行。在第一阶段,它试图为异常找到一个合适的 catch 子句,这可能包括执行保护子句(when、C#6 及更高版本)。在第二阶段,它展开堆栈并根据嵌套要求执行任何 finally 子句,然后到达定义将处理异常的 catch 子句的正确级别1 .

我相信在某些环境中,如果第一阶段无法找到合适的异常处理程序(catch 子句)根本,整个托管环境可能拆了。在这种情况下,不会执行 finally 子句。

符合标准的 CLR 要求 的所有内容都在 ECMA C# and Common Language Infrastructure Standards 上的 MS 分区 I.pdf 文档中进行了描述。第 12.4.2.5 节指出:

When an exception occurs, the CLI searches the array for the first protected block that

  • Protects a region including the current instruction pointer and

  • Is a catch handler block and

  • Whose filter wishes to handle the exception

If a match is not found in the current method, the calling method is searched, and so on. If no match is found the CLI will dump a stack trace and abort the program.

(我的重点)


1使用 的变体进行说明,还有 C# 7 局部函数:

using System;

namespace PlayAreaCSCon
{
    internal class Program
    {
        static void Main(string[] args)
        {
            TestTryCatchFinally();
            Console.WriteLine("Complete");
            Console.ReadLine();
        }

        private static void TestTryCatchFinally()
        {
            try
            {
                Console.WriteLine("Start Outer Try");
                try
                {
                    Console.WriteLine("Start Inner Try");
                    throw new Exception("Exception from inner try");
                }
                finally
                {
                    Console.WriteLine("In Inner Finally");
                }
            }
            catch (Exception ex) when (GuardHelper(ex))
            {
                Console.WriteLine("In outer catch");
            }
            finally
            {
                Console.WriteLine("In outer finally");
            }

            bool GuardHelper(Exception ex)
            {
                Console.WriteLine("In outer guard");
                return true;
            }
        }
    }
}

这会打印:

Start Outer Try
Start Inner Try
In outer guard
In Inner Finally
In outer catch
In outer finally
Complete

说明异常处理的两次通过性质(即 In outer guard 之前 In Inner Finally 打印 In Inner Finally

它应该看起来像这样(取决于您实际尝试做什么)

class Program
{
 public static void Main(string[] args)
 {
     try
     {
         int a = 2;
         int b = 10 / a;

        if (a == 1)
            a = a / a - a;
        if (a == 2)
        {
            int[] c = { 1 };
            c[8] = 9;
        }
    }
    catch (IndexOutOfRangeException e)
    {
         //Do something when something in try block throws error
         Console.WriteLine("B");
    }
    finally
    {
        //This code will ALWAYS execute
        //Even when there is no error, A will be written to console
        Console.WriteLine("A");
    }
    Console.ReadLine();
}
}

我可能遗漏了什么。您不想要以下内容吗?

try
{          
}
catch (Exception ex)
{
}
finally
{  
}

通过使用 finally{} 块,您可以确保某些语句始终 运行

在你的第一个代码中,finally (A) 运行s 在 catch (B) 之前的原因是因为抛出异常时,你退出了内部在外部块的 catch 发挥作用之前块(导致 finally 到 运行)。考虑这段代码:

private void TestTryCatchFinally()
{
    try
    {
        Debug.WriteLine("Start Outer Try");
        try
        {
            Debug.WriteLine("Start Inner Try");
            throw new Exception("Exception from inner try");
            Debug.WriteLine("End of Inner Try - never reaced");
        }
        //remove this catch block for second test
        catch (Exception)
        {
            Debug.WriteLine("In inner catch");
        }
        //end of code to remove
        finally
        {
            Debug.WriteLine("In Inner Finally");
        }
    }
    catch (Exception)
    {
        Debug.WriteLine("In outer catch");
    }
    finally
    {
        Debug.WriteLine("In outer finally");
    }
}

如果我运行这个代码,我得到这个输出:

Start Outer Try
Start Inner Try
Exception thrown: 'System.Exception' in MyTestApp.exe
In inner catch
In Inner Finally
In outer finally

这是您所期望的。但是,如果我删除内部 catch 块(如代码中所述),我将得到以下输出:

Start Outer Try
Start Inner Try
Exception thrown: 'System.Exception' in MyTestApp.exe
In Inner Finally
In outer catch
In outer finally

在这种情况下,一旦执行退出内部 try 块,finally 代码就会执行。然后轮到外面的 catch 块了。