try-catch(C# 参考)

Try-catch 语句包含一个后接一个或多个 catch 子句的 try 块,这些子句指定不同异常的处理程序。

备注

引发异常时,公共语言运行时 (CLR) 查找处理此异常的 catch 语句。如果当前正在执行的方法不包含此类 catch 块,则 CLR 查看调用了当前方法的方法,并以此类推遍历调用堆栈。如果未找到任何 catch 块,则 CLR 向用户显示一条未处理的异常消息,并停止执行程序。

try 块包含可能导致异常的受保护的代码。将执行此块,直至引发异常或其成功完成。例如,强制转换 null 对象的以下尝试会引发 NullReferenceException 异常:

object o2 = null;
try
{
    int i2 = (int)o2;   // Error
}

尽管可以不带参数使用 catch 子句来捕获任何类型的异常,但不推荐这种用法。一般情况下,只应捕获你知道如何从其恢复的异常。因此,应始终指定派生自 System.Exception 的对象参数,例如:

catch (InvalidCastException e) 
{
}

可以使用同一 try-catch 语句中的多个特定 catch 子句。在这种情况下,catch 子句的顺序很重要,因为 catch 子句是按顺序检查的。在使用更笼统的子句之前获取特定性更强的异常。如果捕获块的排序使得永不会达到之后的块,则编译器将产生错误。

筛选想要处理的异常的一种方式是使用 catch 参数。也可以使用谓词表达式进一步检查该异常以决定是否要对其进行处理。如果谓词表达式返回 false,则继续搜索处理程序。

Catch (ArgumentException e) if (e.ParamName == “…”)
{
}

异常筛选器要优于捕获和重新引发(如下所述),因为筛选器将保留堆栈不受损坏。如果之后的处理程序转储堆栈,可以查看到异常的原始来源,而不只是重新引发它的最后一个位置。异常筛选器表达式的一个常见用途是日志记录。可以创建一个始终返回 false 并输出到日志的谓词函数,而且可以在异常通过时进行记录,无需处理并重新引发它们。

可在 catch 块中使用 throw 语句以重新引发已由 catch 语句捕获的异常。下面的示例从 IOException 异常提取源信息,然后向父方法引发异常。

catch (FileNotFoundException e)
{
    // FileNotFoundExceptions are handled here.
}
catch (IOException e)
{
    // Extract some information from this exception, and then 
    // throw it to the parent method.
    if (e.Source != null)
        Console.WriteLine("IOException source: {0}", e.Source);
    throw;
}

你可以捕获一个异常而引发一个不同的异常。执行此操作时,请指定作为内部异常捕获的异常,如以下示例所示。

catch (InvalidCastException e) 
{
    // Perform some action here, and then throw a new exception.
    throw new YourCustomException("Put your error message here.", e);
}

当指定的条件为 true 时,你还可以重新引发异常,如以下示例所示。


catch (InvalidCastException e)
{
    if (e.Data == null)
    {
        throw;
    }
    else
    {
        // Take some action.
    }
 }

try 块内,仅初始化在其中声明的变量。否则,在完成执行块之前,可能会出现异常。例如,在下面的代码示例中,变量 n 在 try 块内部初始化。尝试在 Write(n) 语句的 try 块外部使用此变量将生成编译器错误。

static void Main() 
{
    int n;
    try 
    {
        // Do not initialize this variable here.
        n = 123;
    }
    catch
    {
    }
    // Error: Use of unassigned local variable 'n'.
    Console.Write(n);
}

有关 catch 的详细信息,请参阅 try-catch-finally

异步方法中的异常

异步方法由 async 修饰符标记,通常包含一个或多个 await 表达式或语句。await 表达式将 await 运算符应用于 TaskTask<TResult>

当控件到达异步方法中的 await 时,将挂起方法中的进度,直到所等待的任务完成。任务完成后,可以在方法中恢复执行。有关详细信息,请参阅使用 Async 和 Await 的异步编程(C# 和 Visual Basic)异步程序中的控制流(C# 和 Visual Basic)

应用了 await 的完成任务可能由于返回此任务的方法中存在未处理的异常而处于错误状态。等待该任务引发异常。如果取消了返回任务的异步进程,此任务最后也可能为已取消状态。等待已取消的任务引发 OperationCanceledException。有关如何取消异步进程的详细信息,请参阅 微调异步应用程序(C# 和 Visual Basic)

若要捕获异常,请在 try 块中等待任务并在关联的 catch 块中捕获异常。相关示例,请参见“示例”一节。

任务可能处于错误状态,因为等待的异步方法中发生了多个异常。例如,任务可能是对 Task.WhenAll 调用的结果。当等待此类任务时,仅捕捉到其中一个异常,而且你无法预测将会捕获到哪个异常。相关示例,请参见“示例”一节。

在下面的示例中,try 块包含对可能引发异常的 ProcessString 方法的调用。 catch 子句包含只在屏幕上显示一条消息的异常处理程序。当从 MyMethod 内部调用 throw 语句时,系统将查找 catch 语句并显示消息 Exception caught。

    class TryFinallyTest
{
    static void ProcessString(string s)
    {
        if (s == null)
        {
            throw new ArgumentNullException();
        }
    }

    static void Main()
    {
        string s = null; // For demonstration purposes.

        try
        {            
            ProcessString(s);
        }

        catch (Exception e)
        {
            Console.WriteLine("{0} Exception caught.", e);
        }
    }
}
    /*
    Output:
    System.ArgumentNullException: Value cannot be null.
       at TryFinallyTest.Main() Exception caught.
     * */

在下面的示例中,使用了两个 catch 块,并捕获到最先出现的最具体的异常。

若要捕获最不具体的异常,你可以将 ProcessString 中的 throw 语句替换为以下语句:throw new Exception()。

如果将最不具体的 catch 块置于示例中第一个,将显示以下错误消息:A previous catch clause already catches all exceptions of this or a super type ('System.Exception')。

class ThrowTest3
{
    static void ProcessString(string s)
    {
        if (s == null)
        {
            throw new ArgumentNullException();
        }
    }

    static void Main()
    {
        try
        {
            string s = null;
            ProcessString(s);
        }
        // Most specific:
        catch (ArgumentNullException e)
        {
            Console.WriteLine("{0} First exception caught.", e);
        }
        // Least specific:
        catch (Exception e)
        {
            Console.WriteLine("{0} Second exception caught.", e);
        }
    }
}
/*
 Output:
 System.ArgumentNullException: Value cannot be null.
 at Test.ThrowTest3.ProcessString(String s) ... First exception caught.
*/

下面的示例阐释异步方法的异常处理。若要捕获异步任务引发的异常,将 await 表达式置于 try 块中,并在 catch 块中捕获该异常。

将演示异常处理的示例中的 Throw New Exception 行取消注释。 任务的 IsFaulted 属性设置为 True,任务的 Exception.InnerException 属性设置为异常,并在 catch 块中捕获该异常。

取消注释 throw new OperationCancelledException 行以演示在取消异步进程时发生的情况。任务的 IsCanceled 属性设置为 true,并在 catch 块中捕获异常。在某些不适用于此示例的情况下,任务的 IsFaulted 属性设置为 trueIsCanceled 设置为 false

public async Task DoSomethingAsync()
{
    Task<string> theTask = DelayAsync();

    try
    {
        string result = await theTask;
        Debug.WriteLine("Result: " + result);
    }
    catch (Exception ex)
    {
        Debug.WriteLine("Exception Message: " + ex.Message);
    }
    Debug.WriteLine("Task IsCanceled: " + theTask.IsCanceled);
    Debug.WriteLine("Task IsFaulted:  " + theTask.IsFaulted);
    if (theTask.Exception != null)
    {
        Debug.WriteLine("Task Exception Message: "
            + theTask.Exception.Message);
        Debug.WriteLine("Task Inner Exception Message: "
            + theTask.Exception.InnerException.Message);
    }
}

private async Task<string> DelayAsync()
{
    await Task.Delay(100);

    // Uncomment each of the following lines to
    // demonstrate exception handling.

    //throw new OperationCanceledException("canceled");
    //throw new Exception("Something happened.");
    return "Done";
}

// Output when no exception is thrown in the awaited method:
//   Result: Done
//   Task IsCanceled: False
//   Task IsFaulted:  False

// Output when an Exception is thrown in the awaited method:
//   Exception Message: Something happened.
//   Task IsCanceled: False
//   Task IsFaulted:  True
//   Task Exception Message: One or more errors occurred.
//   Task Inner Exception Message: Something happened.

// Output when a OperationCanceledException or TaskCanceledException
// is thrown in the awaited method:
//   Exception Message: canceled
//   Task IsCanceled: True
//   Task IsFaulted:  False

下面的示例阐释了在多个任务可能导致多个异常的情况中的异常处理。 try 块等待由 Task.WhenAll 的调用返回的任务。应用了 WhenAll 的三个任务完成后,该任务完成。

三个任务中的每一个都会导致异常。 catch 块循环访问异常,这些异常位于由 Task.WhenAll 返回的任务的 Exception.InnerExceptions 属性中。

public async Task DoMultipleAsync()
{
    Task theTask1 = ExcAsync(info: "First Task");
    Task theTask2 = ExcAsync(info: "Second Task");
    Task theTask3 = ExcAsync(info: "Third Task");

    Task allTasks = Task.WhenAll(theTask1, theTask2, theTask3);

    try
    {
        await allTasks;
    }
    catch (Exception ex)
    {
        Debug.WriteLine("Exception: " + ex.Message);
        Debug.WriteLine("Task IsFaulted: " + allTasks.IsFaulted);
        foreach (var inEx in allTasks.Exception.InnerExceptions)
        {
            Debug.WriteLine("Task Inner Exception: " + inEx.Message);
        }
    }
}

private async Task ExcAsync(string info)
{
    await Task.Delay(100);

    throw new Exception("Error-" + info);
}

// Output:
//   Exception: Error-First Task
//   Task IsFaulted: True
//   Task Inner Exception: Error-First Task
//   Task Inner Exception: Error-Second Task
//   Task Inner Exception: Error-Third Task

C# 语言规范

有关详细信息,请参阅 C# 语言规范。该语言规范是 C# 语法和用法的权威资料。

请参阅

C# 参考

C# 编程指南

C# 关键字

try、throw 和 catch 语句 (C++)

异常处理语句(C# 参考)

throw(C# 参考)

try-finally(C# 参考)

如何:显式引发异常