使用异常

在 C# 中,运行时程序中的错误通过使用称为异常的机制传播到程序。 异常由遇到错误的代码引发,由能够更正错误的代码捕捉。 异常可由 .NET 运行时或由程序中的代码引发。 一旦引发异常,异常会沿调用堆栈向上传播,直到找到处理异常的语句。 未捕获的异常由显示对话框的系统提供的泛型异常处理程序处理。

异常由派生自 Exception的类表示。 此类标识异常的类型,并包含详细描述异常的属性。 引发异常涉及创建异常派生类的实例,可以选择配置异常的属性,然后使用关键字引发对象 throw 。 例如:

class CustomException : Exception
{
    public CustomException(string message)
    {
    }
}
private static void TestThrow()
{
    throw new CustomException("Custom exception in TestThrow()");
}

引发异常后,运行时会检查当前语句,以查看它是否位于块中 try 。 如果在,则将检查与 catch 块关联的所有 try 块,以确定它们是否可以捕获该异常。 Catch 块通常指定异常类型;如果块的类型 catch 与异常的类型相同,或者异常的基类,则 catch 块可以处理该方法。 例如:

try
{
    TestThrow();
}
catch (CustomException ex)
{
    System.Console.WriteLine(ex.ToString());
}

如果引发异常的语句不在try块内,或者将其括起来的try块没有相应的catch块,运行时会检查该语句的调用方法中是否包含try语句和catch块。 运行时继续调用堆栈,搜索兼容的 catch 块。 在catch块被找到并执行后,控制权然后传递给该catch块后面的下一个语句。

语句 try 可以包含多个 catch 块。 执行可以处理异常的第一个 catch 语句;即使这些语句兼容,也会忽略以下 catch 任何语句。 按从最具有针对性(或派生程度最高)到最不具有针对性的顺序对 catch 块排列。 例如:

using System;
using System.IO;

namespace Exceptions
{
    public class CatchOrder
    {
        public static void Main()
        {
            try
            {
                using (var sw = new StreamWriter("./test.txt"))
                {
                    sw.WriteLine("Hello");
                }
            }
            // Put the more specific exceptions first.
            catch (DirectoryNotFoundException ex)
            {
                Console.WriteLine(ex);
            }
            catch (FileNotFoundException ex)
            {
                Console.WriteLine(ex);
            }
            // Put the least specific exception last.
            catch (IOException ex)
            {
                Console.WriteLine(ex);
            }
            Console.WriteLine("Done");
        }
    }
}

在执行catch块之前,运行时会检查finally块。 Finally 块使程序员能够清理可能因中止 try 块而留下的任何不明确状态,或释放任何外部资源(如图形句柄、数据库连接或文件流),无需等待运行时的垃圾回收器来最终处理对象。 例如:

static void TestFinally()
{
    FileStream? file = null;
    //Change the path to something that works on your machine.
    FileInfo fileInfo = new System.IO.FileInfo("./file.txt");

    try
    {
        file = fileInfo.OpenWrite();
        file.WriteByte(0xF);
    }
    finally
    {
        // Closing the file allows you to reopen it immediately - otherwise IOException is thrown.
        file?.Close();
    }

    try
    {
        file = fileInfo.OpenWrite();
        Console.WriteLine("OpenWrite() succeeded");
    }
    catch (IOException)
    {
        Console.WriteLine("OpenWrite() failed");
    }
}

如果 WriteByte() 引发异常,则尝试重新打开文件的第二 try 个块中的代码在未调用的情况下 file.Close() 会失败,并且该文件将保持锁定状态。 由于 finally 块会被执行,即使引发异常,因此上一示例中的 finally 块允许文件正确关闭,以帮助避免错误。

如果在引发异常后在调用堆栈上找不到兼容的 catch 块,则会发生以下三种情况之一:

  • 如果异常位于 终结器中,则终止终结器,并调用基终结器(如果有)。
  • 如果调用堆栈包含静态构造函数或静态字段初始值设定项,则会引发一个 TypeInitializationException ,并将原始异常分配给 InnerException 新异常的属性。
  • 如果到达线程的起始位置,线程就会被终止。