Try...Catch...Finally语句 (Visual Basic)

提供了一种方法,可用于处理给定代码块中可能发生的一些或全部可能错误,同时保持代码运行。

语法

Try
    [ tryStatements ]
    [ Exit Try ]
[ Catch [ exception [ As type ] ] [ When expression ]
    [ catchStatements ]
    [ Exit Try ] ]
[ Catch ... ]
[ Finally
    [ finallyStatements ] ]
End Try

组成部分

术语 定义
tryStatements 可选。 可能发生错误的语句。 可以是复合语句。
Catch 可选。 允许使用多个 Catch 块。 如果在处理 Try 块时发生异常,则按文本顺序检查每个 Catch 语句,以确定它是否处理该异常(其中 exception 表示已引发的异常)。
exception 可选。 任何变量名称。 exception 的初始值是引发的错误的值。 与 Catch 结合使用,指定捕获的错误。 如果省略,则 Catch 语句会捕获任何异常。
type 可选。 指定类筛选器的类型。 如果 exception 的值属于 type指定的类型或是属于派生类型,则标识符会绑定到异常对象。
When 可选。 具有 When 子句的 Catch 语句仅在 expression 计算结果为 True 时才捕获异常。 When 子句仅在检查异常类型后才会进行应用,expression 可能引用表示异常的标识符。
expression 可选。 必须可隐式转换为 Boolean。 描述泛型筛选器的任何表达式。 通常用于按错误号进行筛选。 与 When 关键字结合使用,指定捕获错误时的情况。
catchStatements 可选。 用于处理在关联 Try 块中发生的错误的语句。 可以是复合语句。
Exit Try 可选。 突破 Try...Catch...Finally 结构的关键字。 执行会继续运行紧跟在 End Try 语句后的代码。 Finally 语句仍会执行。 不允许在 Finally 块中使用。
Finally 可选。 在执行离开 Try...Catch 语句的任何部分时,始终会执行 Finally 块。
finallyStatements 可选。 进行所有其他错误处理后执行的语句。
End Try 终止 Try...Catch...Finally 结构。

注解

如果预计特定异常可能在代码的特定部分中发生,请将代码置于 Try 块中,并使用 Catch 块保留控制并处理异常(如果发生)。

Catch 语句包含一个后接一个或多个 Try 子句的 Try…Catch 块,这些子句为各种异常指定处理程序。 在 Try 块中引发异常时,Visual Basic 会查找处理该异常的 Catch 语句。 如果找不到匹配的 Catch 语句,则 Visual Basic 会检查调用当前方法的方法,并以此类推遍历调用堆栈。 如果未找到任何 Catch 块,则 Visual Basic 向用户显示一条未经处理的异常消息,并停止执行程序。

可以在 Try…Catch 语句中使用多个 Catch 语句。 如果这样做,则 Catch 子句的顺序会十分重要,因为是按顺序检查它们。 在使用更笼统的子句之前获取特定性更强的异常。

以下 Catch 语句条件最不具体,会捕获从 Exception 类派生的所有异常。 通常在捕获预计的所有特定异常后,应使用这些变体之一作为 Try...Catch...Finally 结构中的最后一个 Catch 块。 控制流绝不可能到达跟在其中任一变体后的 Catch 块。

  • typeException,例如:Catch ex As Exception

  • 语句没有 exception 变量,例如:Catch

Try…Catch…Finally 嵌套在另一个 Try 块中时,Visual Basic 会首先检查最内层 Try 块中的每个 Catch 语句。 如果未找到匹配的 Catch 语句,则会继续搜索外层 Try…Catch…Finally 块的 Catch 语句。

Try 块中的局部变量在 Catch 块中不可用,因为它们是单独的块。 如果要在多个块中使用变量,请在 Try...Catch...Finally 结构外部声明变量。

提示

Try…Catch…Finally 语句可用作 IntelliSense 代码片段。 在代码片段管理器中,展开代码模式“代码模式 - If、For Each、TryCatch、Property 等”,然后展开“错误处理(异常)”。 有关详细信息,请参阅代码片段

Finally 块

如果有一个或多个语句必须在退出 Try 结构之前运行,请使用 Finally 块。 控制恰好在传出 Try…Catch 结构之前传递到 Finally 块。 即使在 Try 结构中的任何位置发生异常,情况也是如此。

Finally 块可用于运行即使发生异常也必须执行的任何代码。 无论 Try...Catch 块如何退出,控制都会传递到 Finally 块。

即使代码在 TryCatch 块中遇到 Return 语句,Finally 块中的代码也会运行。 在以下情况下,控制不会从 TryCatch 块传递到对应的 Finally 块:

将执行显式转移到 Finally 块中是无效的。 将执行从 Finally 块中转移离开是无效的(除非通过异常进行)。

如果 Try 语句未包含至少一个 Catch 块,则它必须包含 Finally 块。

提示

如果无需捕获特定异常,则 Using 语句的行为类似于 Try…Finally 块,可保证资源处置(无论如何退出块)。 即使出现未经处理的异常也是如此。 有关详细信息,请参阅 Using 语句

异常参数

Catchexception 参数是 Exception 类或从 Exception 类派生的类的实例。 Exception 类实例对应于 Try 块中发生的错误。

Exception 对象的属性可帮助确定异常的原因和位置。 例如,StackTrace 属性会列出导致异常的被调用方法,从而帮助找到代码中发生错误的位置。 Message 返回描述异常的消息。 HelpLink 返回指向关联帮助文件的链接。 InnerException 返回导致当前异常的 Exception 对象,如果没有原始 Exception,则返回 Nothing

使用 Try…Catch 语句时的注意事项

使用 Try…Catch 语句仅指示发生了异常或意外程序事件。 形成这种情况的原因如下:

  • 在运行时捕获异常会形成额外开销,并且速度可能比进行预检查以避免异常要慢。

  • 如果未正确处理 Catch 块,则可能无法正确地向用户报告异常。

  • 异常处理使程序更加复杂。

并不总是需要 Try…Catch 语句来检查可能会发生的条件。 以下示例在尝试打开文件之前检查文件是否存在。 这可减少对捕获 OpenText 方法引发的异常的需要。

Private Sub TextFileExample(ByVal filePath As String)

    ' Verify that the file exists.
    If System.IO.File.Exists(filePath) = False Then
        Console.Write("File Not Found: " & filePath)
    Else
        ' Open the text file and display its contents.
        Dim sr As System.IO.StreamReader =
            System.IO.File.OpenText(filePath)

        Console.Write(sr.ReadToEnd)

        sr.Close()
    End If
End Sub

确保 Catch 块中的代码可以正确地向用户报告异常(无论是通过线程安全日志记录还是适当的消息)。 否则,异常可能仍保持未知状态。

异步方法

如果用 Async 修饰符标记方法,则可以在该方法中使用 Await 运算符。 具有 Await 运算符的语句会暂停方法的执行,直到等待的任务完成。 任务表示正在进行的工作。 在与 Await 运算符关联的任务完成时,在同一方法中继续执行。 有关详细信息,请参阅异步程序中的控制流

异步方法返回的任务可能以出错状态结束,从而指示由于未经处理的异常而完成。 任务也可能以已取消状态结束,这会导致从 await 表达式中引发 OperationCanceledException。 若要捕获任一类型的异常,请将与任务关联的 Await 表达式置于在 Try 块中,并在 Catch 块中捕获异常。 本主题后面部分会提供一个示例。

任务可能处于出错状态,因为有多个异常导致其出错。 例如,任务可能是对 Task.WhenAll 调用的结果。 当等待此类任务时,捕获的异常只是异常中的一个,而且你无法预测将会捕获到哪个异常。 本主题后面部分会提供一个示例。

Await 表达式不能位于 Catch 块或 Finally 块内。

迭代器

迭代器函数或 Get 访问器可对集合执行自定义迭代。 迭代器使用 yield 语句返回集合的每一个元素,每次返回一个元素。 使用 For Each...Next 语句调用迭代器函数。

Yield 语句可以位于 Try 块内。 包含 Yield 语句的 Try 块可以具有 Catch 块,并且可以具有 Finally 块。 有关示例,请参阅 Try 块

Yield 语句不能位于 Catch 块或 Finally 块内。

如果 For Each 主体(在迭代器函数之外)引发异常,则不会执行迭代器函数中的 Catch 块,但会执行迭代器函数中的 Finally 块。 迭代器函数内的 Catch 块仅捕获迭代器函数内发生的异常。

部分信任情况

在部分信任情况下(如网络共享上托管的应用程序),Try...Catch...Finally 不会捕获在调用包含调用的方法之前发生的安全性异常。 以下示例在置于服务器共享中并从中运行时,会生成错误“System.Security.SecurityException: 请求失败”。有关安全性异常的详细信息,请参阅 SecurityException 类。

Try
    Process.Start("http://www.microsoft.com")
Catch ex As Exception
    Console.WriteLine("Can't load Web page" & vbCrLf & ex.Message)
End Try

在此类部分信任情况下,必须将 Process.Start 语句置于单独的 Sub 中。 对 Sub 的初始调用会失败。 这使 Try...Catch 可以在启动包含 Process.StartSub 并生成安全性异常之前捕获它。

示例

Try...Catch...Finally 的结构

下面的示例阐释了 Try...Catch...Finally 语句的结构。

Public Sub TryExample()
    ' Declare variables.
    Dim x As Integer = 5
    Dim y As Integer = 0

    ' Set up structured error handling.
    Try
        ' Cause a "Divide by Zero" exception.
        x = x \ y

        ' This statement does not execute because program
        ' control passes to the Catch block when the
        ' exception occurs.
        Console.WriteLine("end of Try block")
    Catch ex As Exception
        ' Show the exception's message.
        Console.WriteLine(ex.Message)

        ' Show the stack trace, which is a list of methods
        ' that are currently executing.
        Console.WriteLine("Stack Trace: " & vbCrLf & ex.StackTrace)
    Finally
        ' This line executes whether or not the exception occurs.
        Console.WriteLine("in Finally block")
    End Try
End Sub

Try 块调用的方法中的异常

在下面的示例中,CreateException 方法引发了 NullReferenceException。 生成异常的代码不在 Try 块中。 因此,CreateException 方法不处理异常。 RunSample 方法会处理异常,因为对 CreateException 方法的调用位于 Try 块中。

该示例包含用于多种异常类型的 Catch 语句(按照从最具体到最一般的顺序排序)。

Public Sub RunSample()
    Try
        CreateException()
    Catch ex As System.IO.IOException
        ' Code that reacts to IOException.
    Catch ex As NullReferenceException
        Console.WriteLine("NullReferenceException: " & ex.Message)
        Console.WriteLine("Stack Trace: " & vbCrLf & ex.StackTrace)
    Catch ex As Exception
        ' Code that reacts to any other exception.
    End Try
End Sub

Private Sub CreateException()
    ' This code throws a NullReferenceException.
    Dim obj = Nothing
    Dim prop = obj.Name

    ' This code also throws a NullReferenceException.
    'Throw New NullReferenceException("Something happened.")
End Sub

Catch When 语句

下面的示例演示如何使用 Catch When 语句筛选条件表达式。 如果条件表达式计算结果为 True,则 Catch 块中的代码会运行。

Private Sub WhenExample()
    Dim i As Integer = 5

    Try
        Throw New ArgumentException()
    Catch e As OverflowException When i = 5
        Console.WriteLine("First handler")
    Catch e As ArgumentException When i = 4
        Console.WriteLine("Second handler")
    Catch When i = 5
        Console.WriteLine("Third handler")
    End Try
End Sub
' Output: Third handler

嵌套 Try 语句

下面的示例有一个包含在 Try 块中的 Try…Catch 语句。 内部 Catch 块会引发一个异常,该异常的 InnerException 属性设置为原始异常。 外部 Catch 块会报告其自己的异常和内部异常。

Private Sub InnerExceptionExample()
    Try
        Try
            ' Set a reference to a StringBuilder.
            ' The exception below does not occur if the commented
            ' out statement is used instead.
            Dim sb As System.Text.StringBuilder
            'Dim sb As New System.Text.StringBuilder

            ' Cause a NullReferenceException.
            sb.Append("text")
        Catch ex As Exception
            ' Throw a new exception that has the inner exception
            ' set to the original exception.
            Throw New ApplicationException("Something happened :(", ex)
        End Try
    Catch ex2 As Exception
        ' Show the exception.
        Console.WriteLine("Exception: " & ex2.Message)
        Console.WriteLine(ex2.StackTrace)

        ' Show the inner exception, if one is present.
        If ex2.InnerException IsNot Nothing Then
            Console.WriteLine("Inner Exception: " & ex2.InnerException.Message)
            Console.WriteLine(ex2.StackTrace)
        End If
    End Try
End Sub

异步方法的异常处理

下面的示例阐释异步方法的异常处理。 为了捕获适用于异步任务的异常,Await 表达式位于调用方的 Try 块中,异常在 Catch 块中进行捕获。

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

取消注释 Throw New OperationCancelledException 行以演示在取消异步进程时发生的情况。 异常在 Catch 块中进行捕获,任务的 IsCanceled 属性设置为 True。 但是在某些不适用于此示例的情况下,IsFaulted 设置为 TrueIsCanceled 设置为 False

Public Async Function DoSomethingAsync() As Task
    Dim theTask As Task(Of String) = DelayAsync()

    Try
        Dim result As String = Await theTask
        Debug.WriteLine("Result: " & result)
    Catch ex As Exception
        Debug.WriteLine("Exception Message: " & ex.Message)
    End Try

    Debug.WriteLine("Task IsCanceled: " & theTask.IsCanceled)
    Debug.WriteLine("Task IsFaulted:  " & theTask.IsFaulted)
    If theTask.Exception IsNot Nothing Then
        Debug.WriteLine("Task Exception Message: " &
            theTask.Exception.Message)
        Debug.WriteLine("Task Inner Exception Message: " &
            theTask.Exception.InnerException.Message)
    End If
End Function

Private Async Function DelayAsync() As Task(Of String)
    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"
End Function


' 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 an OperationCanceledException or TaskCanceledException
' is thrown in the awaited method:
'   Exception Message: canceled
'   Task IsCanceled: True
'   Task IsFaulted:  False

处理异步方法中的多个异常

下面的示例阐释了在多个任务可能导致多个异常的情况中的异常处理。 对于 Task.WhenAll 返回的任务,Try 块具有 Await 表达式。 应用了 Task.WhenAll 的三个任务完成后,该任务完成。

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

Public Async Function DoMultipleAsync() As Task
    Dim theTask1 As Task = ExcAsync(info:="First Task")
    Dim theTask2 As Task = ExcAsync(info:="Second Task")
    Dim theTask3 As Task = ExcAsync(info:="Third Task")

    Dim allTasks As Task = Task.WhenAll(theTask1, theTask2, theTask3)

    Try
        Await allTasks
    Catch ex As Exception
        Debug.WriteLine("Exception: " & ex.Message)
        Debug.WriteLine("Task IsFaulted: " & allTasks.IsFaulted)
        For Each inEx In allTasks.Exception.InnerExceptions
            Debug.WriteLine("Task Inner Exception: " + inEx.Message)
        Next
    End Try
End Function

Private Async Function ExcAsync(info As String) As Task
    Await Task.Delay(100)

    Throw New Exception("Error-" & info)
End Function

' 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

另请参阅