异常处理 (C# 编程指南)

C# 程序员使用 try 块对可能受异常影响的代码进行分区。 关联的 catch 块用于处理任何生成的异常。 finally块包含运行的代码,无论try块中是否抛出异常,该代码都会执行,比如释放在try块中分配的资源。 块 try 需要一个或多个关联的 catch 块,或者一个 finally 块或两者兼有。

以下示例演示语句 try-catchtry-finally 语句和 try-catch-finally 语句。

try
{
    // Code to try goes here.
}
catch (SomeSpecificException ex)
{
    // Code to handle the exception goes here.
    // Only catch exceptions that you know how to handle.
    // Never catch base class System.Exception without
    // rethrowing it at the end of the catch block.
}
try
{
    // Code to try goes here.
}
finally
{
    // Code to execute after the try block goes here.
}
try
{
    // Code to try goes here.
}
catch (SomeSpecificException ex)
{
    // Code to handle the exception goes here.
}
finally
{
    // Code to execute after the try (and possibly catch) blocks
    // goes here.
}

缺少try块或catch块的finally块会导致编译器错误。

catch 块

catch 可以指定要捕获的异常类型。 类型规范称为 异常筛选器。 异常类型应派生自 Exception。 一般情况下,请勿将Exception指定为异常筛选器,除非你知道如何处理可能在try代码块中引发的所有异常,或者已在throw代码块末尾包含catch

可以将具有不同异常类的多个 catch 块链接在一起。 这些 catch 块在代码中从上到下进行评估,但针对引发的每个异常只执行一个 catch 块。 第一个指定抛出异常的确切类型或基类的 catch 块被执行。 如果没有 catch 块指定了匹配的异常类,则会选择一个没有任何类型的 catch 块(如果语句中存在)。 务必首先定位具有最具体的(即,最底层派生的)异常类的 catch 块。

当以下条件为 true 时,捕获异常:

  • 你可以很好地了解引发异常的原因,并且可以实现特定的恢复,例如在捕获 FileNotFoundException 对象时提示用户输入新文件名。
  • 可以创建和引发一个新的、更具体的异常。
    int GetInt(int[] array, int index)
    {
        try
        {
            return array[index];
        }
        catch (IndexOutOfRangeException e)
        {
            throw new ArgumentOutOfRangeException(
                "Parameter index is out of range.", e);
        }
    }
    
  • 想要先对异常进行部分处理,然后再将其传递以进行更多处理。 在以下示例中, catch 块用于在重新引发异常之前将条目添加到错误日志。
    try
    {
        // Try to access a resource.
    }
    catch (UnauthorizedAccessException e)
    {
        // Call a custom error logging procedure.
        LogError(e);
        // Re-throw the error.
        throw;
    }
    

还可以指定 异常筛选器 ,将布尔表达式添加到 catch 子句。 异常筛选器指示仅当该条件为 true 时,特定的 catch 子句才匹配。 在以下示例中,这两个 catch 子句使用相同的异常类,但会检查额外的条件以创建不同的错误消息:

int GetInt(int[] array, int index)
{
    try
    {
        return array[index];
    }
    catch (IndexOutOfRangeException e) when (index < 0) 
    {
        throw new ArgumentOutOfRangeException(
            "Parameter index cannot be negative.", e);
    }
    catch (IndexOutOfRangeException e)
    {
        throw new ArgumentOutOfRangeException(
            "Parameter index cannot be greater than the array size.", e);
    }
}

始终返回 false 的异常筛选器可用于检查所有异常,但不能处理这些异常。 典型的用途是记录异常:

public class ExceptionFilter
{
    public static void Main()
    {
        try
        {
            string? s = null;
            Console.WriteLine(s.Length);
        }
        catch (Exception e) when (LogException(e))
        {
        }
        Console.WriteLine("Exception must have been handled");
    }

    private static bool LogException(Exception e)
    {
        Console.WriteLine($"\tIn the log routine. Caught {e.GetType()}");
        Console.WriteLine($"\tMessage: {e.Message}");
        return false;
    }
}

LogException 方法始终返回 false,使用此异常筛选器的 catch 子句均不匹配。 catch 子句可以是通用的,使用 System.Exception,后面的子句可以处理更具体的异常类。

Finally 块

使用 finally 块可以清理在 try 块中执行的操作。 如果存在,finally 块将在 try 块和任何匹配的 catch 块之后最后执行。 无论是否会引发异常或找到匹配异常类型的 finally 块,catch 块都将始终运行。

finally 可用于释放文件流、数据库连接和图形句柄等资源,而无需等待运行时中的垃圾回收器完成这些对象。

在以下示例中,该 finally 块用于关闭在 try 块中打开的文件。 请注意,在关闭文件之前,将检查文件句柄的状态。 try如果块无法打开文件,则文件句柄仍具有值null,并且finally该块不会尝试关闭它。 相反,如果文件在块中 try 成功打开,则 finally 块会关闭打开的文件。

FileStream? file = null;
FileInfo fileinfo = new System.IO.FileInfo("./file.txt");
try
{
    file = fileinfo.OpenWrite();
    file.WriteByte(0xF);
}
finally
{
    // Check for null because OpenWrite might have failed.
    file?.Close();
}

C# 语言规范

有关详细信息,请参阅 C# 语言规范中的 Exceptionstry 语句。 语言规范是 C# 语法和用法的明确来源。

另请参阅