65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 Try... Catch... Finally!

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.80/5 (45投票s)

2011年2月5日

CPOL

18分钟阅读

viewsIcon

328710

downloadIcon

2415

本文描述了在代码中正确使用 Try... Catch... Finally 和 Using... End Using 块的多种实现方法。

引言

错误处理是任何软件开发人员工作的重要组成部分。.NET Framework 提供了一些非常强大的工具,可以相对轻松地处理错误。
其中一个工具就是 Try... Catch... Finally... 语句。然而,在我编程的这几个月里,我看到不少(错误)使用 Try... Catch... Finally... 的情况,不仅是初学者,甚至连所谓的资深开发者和技术专家也会这样做!
有很多书籍和文章讨论 .NET 中正确的错误处理,但许多都未能提供一个简单而完整的关于这个极其重要的 Try... Catch... Finally... 块的概述。
通过本文,我希望能向任何软件开发者介绍 Try... Catch... Finally... 块的一些正确且详细的用法。

使用示例项目

本文我制作了一个小型示例应用程序,处理 Try... Catch... Finally... 语句的各种用法。该示例易于使用和调试。
该示例包含一个带有几个按钮的窗体。每个按钮都演示了 Try... Catch... Finally... 块的特定用法,您应该使用断点进行调试以完全理解。请注意,代码本身实际上什么都没做,它只是抛出异常并处理它们。

该项目是使用 Visual Studio 2010 Express 版和 .NET Framework 4.0 创建的。但是,所有代码都可以轻松地用于任何 .NET 3.5 应用程序。

UsingTryCatchFinally.jpg

关于异常的一些基本知识

无论您如何测试应用程序,错误最终总是会发生。也许数据库连接失败,或者您试图打开一个用户计算机上不存在的文件,或者用户没有访问权限的文件。如果发生此类错误,.NET Framework 会收集有关刚刚发生错误的详细信息,并将其存储在一个 Exception 对象中,然后该对象会被向上(或向下?)抛出到堆栈中。.NET Framework 知道许多类型的异常,例如 FileNotFoundExceptionIndexOutOfRangeException SqlException。其中一些 Exception 类提供了关于刚刚发生情况的具体信息。例如,SqlException 包含有关 SqlError 严重程度的信息。

无论抛出哪种类型的异常,它们都继承自 System.Exception 类。当 .NET Framework 抛出 Exception 时,它会自动在堆栈中查找第一个 Catch 块(包含在 Try... Catch... 块中)。如果堆栈中不包含任何 Catch,则会发生未处理的异常。这将向最终用户显示一个提示,告知他们发生了未处理的异常,他们可以选择继续忽略错误或退出应用程序。接下来会发生什么是个谜。最好的情况是,您的应用程序无论如何都会关闭。最坏的情况……就别想了 :)。
作为开发人员,您通常不希望用户看到这样的消息。因此,通过使用 Try... Catch... 块在代码中实现错误处理非常重要。

启动示例项目

打开示例项目时,您将看到一个带有多个按钮的窗体。
我知道那个大红按钮看起来很诱人,但我们先不要按它。首先,生成项目。然后转到您的调试文件夹(通常位于您的解决方案文件夹中)并运行可执行文件。现在您可以按那个大红按钮了。看起来不太好,是吧?
现在按任何其他按钮(“关闭”除外),并注意区别。再次回到项目文件。在运行应用程序之前,我想让您在 Visual Studio 中按下快捷键 Control + Alt + E。这将显示“异常对话框”。请确保“通用语言运行时异常”后面的两个复选框都已选中。这将确保在代码中抛出任何异常时,您会进入调试模式。建议您使用断点和 F8 键完全逐步执行任何按钮的代码,以跟踪异常在处理 Try... Catch... Finally 块时所经过的路径。

一个简单的 Try... Catch...

打开项目并按下窗体上的第一个按钮,“简单的 Try... Catch....”。您将立即被带到源代码。这并不是一段很精彩的代码,但一切都始于此。

Private Sub btnSimpleTryCatch_Click(ByVal sender As System.Object, _
	ByVal e As System.EventArgs) Handles btnSimpleTryCatch.Click
        Try
            ' Some code here...
            ' Oops! An error occurs!
            Throw New System.Exception("An unexpected error occurred")
            ' Some more code here... This will not be executed if the Exception occurs.
        Catch ex As Exception
            ' Handle the exception.
            MessageBox.Show(ex.Message, Me.Text, _
		MessageBoxButtons.OK, MessageBoxIcon.Error)
        End Try
    End Sub

注意发生了什么。我正在尝试(Try...)运行一些代码,但由于某种原因,抛出了一个 Exception。我的应用程序没有崩溃,而是 Exception 寻找它能找到的第一个 Catch。幸运的是,附近有一个 Catch Exception 得到了处理。请注意,Throw Catch 之间的任何代码都不会执行。Exception 会跳过这段代码,直接转到 Catch 块。另外请注意,我捕获了 ex 作为 Exception。还记得吗,每种类型的 Exception 都继承自 Exception 对象?这意味着这段代码可以捕获任何 Exception!在这种情况下,我只是通过使用 MessageBox 并显示 Exception 对象的 Message 属性来告知用户发生了问题。现在,Try... Catch... 之间的任何代码以及可能出现的任何问题都会通过向用户显示提示来自动处理。很简单,不是吗?

一个简单的 Try... Catch... Finally...

让我们继续到窗体上的第二个按钮。点击它,您会注意到光标变成了等待光标!不幸的是,几秒钟后,一切都出错了……

Private Sub btnSimpleTryCatchFinally_Click(ByVal sender As System.Object, _
	ByVal e As System.EventArgs) Handles btnSimpleTryCatchFinally.Click
        ' The cursor goes into waiting mode.
        Me.Cursor = Cursors.WaitCursor
        Try
            ' Some code here...
            ' This is some heavy code!
            Threading.Thread.Sleep(5000)
            ' Oops! An error occurs!
            Throw New System.Exception("An unexpected error occurred.")
            ' Some more code here...
        Catch ex As Exception
            ' Handle the exception.
            MessageBox.Show(ex.Message, Me.Text, MessageBoxButtons.OK, MessageBoxIcon.Error)
        Finally
            ' Make sure the cursor is set back to default!
            Me.Cursor = Cursors.Default
        End Try
    End Sub

这段代码与前一个按钮的代码非常相似,唯一的区别是这是一个耗时过程,我们将光标变成了等待光标。我经常等待那些看起来正在加载但实际上已崩溃并导致我的光标始终处于等待模式的应用程序!那么,我们如何确保我们应用程序中的光标始终恢复为默认状态呢?很简单,我们使用 Finally... 块。Try... Catch... Finally... 块中的 Finally... 语句总是会被执行。所以,如果一切顺利,我的代码将被执行,它会跳过 Catch 块,进入我的 Finally 块,然后将我的光标恢复正常。如果发生 Exception (就像这里一样),代码会跳转到 Catch 块,处理错误,然后进入我的 Finally 块,确保我的光标恢复正常。请注意,我最初是在 Try... Catch... Finally... 块之外更改我的光标的。这可以确保当我进入 Finally 时,我的光标实际上是一个等待光标。在此示例中,第一行代码是在 Try... Catch... Finally... 块内还是外,都不会有影响。但稍后您会看到,这会有影响。我还要提一下,Try... Finally... 块也可以不带 Catch 使用。稍后我们将看到一个示例。

多个 Catches

在某些情况下,您可能希望以不同于其他异常的方式处理某些 Exception。下面的示例提供了对单个 Try... Catch... 块中多个 Catches 的解释。对于这个例子,我们就说我们正在尝试打开用户计算机上的一个文件。任何事情都可能发生。也许您尝试打开的文件的目录不存在,也许文件不存在,也许用户没有权限打开该文件,或者发生了完全意想不到的事情。在这种情况下,您可以使用多个 catches,如下面的代码片段所示:

Private Sub btnMultipleCatches_Click(ByVal sender As System.Object, _
	ByVal e As System.EventArgs) Handles btnMultipleCatches.Click
        Try
            ' Some code here...
            ' Let's say we're trying to open a file here.
            ' Oops! An error occurs!
            Dim rand As New Random
            If rand.Next(2) = 1 Then
                Throw New IO.FileNotFoundException_
		("The file you are trying to open was not found.")
            Else
                Throw New System.Exception("An unknown error occurred.")
            End If
            ' Some more code here...
        Catch dirEx As IO.DirectoryNotFoundException
            ' Handle the specific DirectoryNotFoundException here.
            MessageBox.Show(dirEx.Message, "Directory not found", _
		MessageBoxButtons.OK, MessageBoxIcon.Error)
        Catch fileEx As IO.FileNotFoundException
            ' Handle the specific FileNotFoundException here.
            MessageBox.Show(fileEx.Message, "File not found", _
		MessageBoxButtons.OK, MessageBoxIcon.Error)
        Catch ioEx As IO.IOException
            ' Handle other non-specific IO Exceptions here.
            MessageBox.Show(ioEx.Message, "IO exception", _
		MessageBoxButtons.OK, MessageBoxIcon.Error)
        Catch ex As Exception
            ' Handle any other non-IO Exception here.
            MessageBox.Show(ex.Message, "Unknown exception", _
		MessageBoxButtons.OK, MessageBoxIcon.Error)
        End Try
    End Sub

注意,Exception 是如何使用 Random 类生成的。50% 的情况下,我们会得到一个 FileNotFoundException ,50% 的情况下,我们会得到一个非特定的 Exception
此时,您可能需要取消选中在本文开头使用 Control + Alt + E 勾选的复选框,然后按几次“Multiple catches”按钮,看看它如何显示两个不同的 MessageBoxes(“文件未找到”或“未知异常”MessageBoxes)。

如果抛出 Exception ,它会查找第一个可用的 Catch 块。在这种情况下,对于 FileNotFoundException ,它是 Catch fileEx As IO.FileNotFoundException。请注意,FileNotFoundException 会跳过 Catch dirEx As IO.DirectoryNotFoundException 块,因为它不是 DirectoryNotFoundException。同样,如果 fileEx 已经在 fileEx 块中处理,它将不再进入 ioEx 或 ex 块。

FileNotFoundException DirectoryNotFoundException 都继承自 IOException。这意味着如果我将 Catch ioEx As IO.IOException 块放在其他两个 Catch 块的上面,FileNotFoundException DirectoryNotFoundException 将永远不会跳入它们各自的 Catch 块!
处理多种类型的 Exception 时,您必须首先指定最具体的 Exception 块。通用的 Catch ex as Exception 应该始终放在层次结构的底部,因为每个 Exception 都可以跳入这个块,阻止它们跳入其后的更具体的 Catch 块。

Finally... 块仍然可以添加到 Try... Catch... 块的底部。
如果您取消选中了使用 Control + Alt + E 勾选的“Common Language Runtime Exception”复选框,请不要忘记再次将其勾选。

来自更深层方法的异常

关于 Try... Catch... 的一个普遍误解是,每个代码块都应该有一个。在许多(如果不是大多数)情况下,这并不正确。为了说明这一点,我创建了一个名为“ManyMethods”的类,它暴露了三个 Public 成员,这些成员会进入几个方法。除了一个方法外,其他方法都不包含 Try... Catch... 块。然而,最终所有这些方法都会调用的最后一个方法总是会抛出一个 Exception。如果所有方法都没有 Try... Catch... 块,那么这个 Exception 将在哪里被处理?很简单,在堆栈上的第一个包含 Try... Catch... 块的代码块中。在这种情况下,就是按钮的事件处理程序。

    Private Sub btnExceptionsFromDeeperMethods_Click(ByVal sender As System.Object, _
	ByVal e As System.EventArgs) Handles btnExceptionsFromDeeperMethods.Click
        Try
            Dim mm As New ManyMethods
            ' This method goes into three other methods!
            mm.GoIntoDeeperMethods()
        Catch ex As Exception
            MessageBox.Show(ex.Message & Environment.NewLine & _
                            "The method which threw me was: " & ex.TargetSite.Name, _
                            Me.Text, MessageBoxButtons.OK, MessageBoxIcon.Error)
        End Try
    End Sub

	Public Sub GoIntoDeeperMethods()
        B() ' Goes into B, which goes into C, which goes into ExceptionMethod,
            ' which throws an error.
        MessageBox.Show("You will never see me!", "Some deeper method", _
		MessageBoxButtons.OK, MessageBoxIcon.Information)
        AA() ' Will never be executed.
        AB() ' Will never be executed.
    End Sub

	' Method B, C, AA and AB show MessageBoxes,
         ' but only after eventually calling the ExceptionMethod.
	' The MessageBoxes will therefore never be prompted.

	Private Sub ExceptionMethod()
        ' Some code here...
        ' Oops! An error occurs!
        Throw New System.Exception("I am coming from way down here!") ' This returns
		' to the first Catch block, in the case in the button Event Handler.
        ' Some code here...
    End Sub

所以,即使这段代码调用的任何一个方法都没有 Try... Catch... 块,Exception 也会简单地返回这里,让用户知道出了问题。
还请注意,我使用了 Exception 对象的 'TargetSite' 属性。此属性包含抛出 Exception 的方法(有关更多信息,请参阅 System.Reflection.MethodBase http://msdn.microsoft.com/en-us/library/system.reflection.methodbase.aspx 命名空间)。我使用此属性来证明 Exception 确实是在调用方法中的几个方法抛出的,但是,如果发生 Exception ,它可以在堆栈中的第一个 Try... Catch... 块中得到处理,无论它在哪里。

同时检查 ManyMethods 类中的其他方法。它们都由代码执行过程中调用的方法调用。然而,由于发生了 Exception ,调用代码永远不会执行。

更深层方法中的 Finally

这个示例与上一个几乎相同,只有一个区别。在这个示例中,我在我的按钮事件处理程序和最终调用 ExceptionMethod的方法之间插入了一个方法。这个额外的方法包含一个 Try... Finally... 块。所以点击按钮,看看会发生什么。

Private Sub btnFinallyInDeeperMethods_Click(ByVal sender As System.Object, _
	ByVal e As System.EventArgs) Handles btnFinallyInDeeperMethods.Click
        Try
            Dim mm As New ManyMethods
            ' This method goes into four other methods!
            mm.GoIntoDeeperMethodsWithFinally()
        Catch ex As Exception
            MessageBox.Show(ex.Message & Environment.NewLine & _
                            "The method which threw me was: " & ex.TargetSite.Name, _
                            Me.Text, MessageBoxButtons.OK, MessageBoxIcon.Error)
        End Try
    End Sub

	Public Sub GoIntoDeeperMethodsWithFinally()
        Try
            ' This will eventually throw an exception.
            GoIntoDeeperMethods()
        Finally
            ' This code will ALWAYS be executed.
            MessageBox.Show("You will always see me!", _
		"Finally", MessageBoxButtons.OK, MessageBoxIcon.Information)
        End Try
    End Sub

您可以看到,当您按下按钮并逐步执行代码时,此代码块中的 Finally... 语句将被执行,即使 Exception 未在此处抛出或处理。在 Exception 被抛出和处理之间(的任何) Finally... 块将始终被执行。这在使用 Dispose 对象或修改用户界面(例如在第一个 Finally... 示例中更改光标)时尤其有用。

多次处理异常

当一个 Exception 从一个被 Form 调用的 Class 抛出时,您可能希望在将 Exception 向上抛出给调用 Form 以告知用户出了问题之前,先在抛出的 Class 中进行一些 Exception 处理。

一个实际的例子是使用 Transaction 对象将数据存储到数据库的 Class 。当发生错误时,您希望在将 Exception 抛出到调用 Form 之前回滚事务。

Finally... 块不足以解决问题,因为您不知道事务是需要回滚还是提交。在 Exception 类本身中处理异常是必要的,但异常只能处理一次,并且不会被抛出到调用 Form
基本上有两种选择可以考虑。您可以在处理完 Exception 后重新抛出它,或者抛出一个新的 Exception ,可能将原始 Exception 作为 InnerException 传递给新的 Exception。下面的代码片段显示了后一种选择。

Private Sub btnHandlingAnExceptionMultipleTimes_Click_
	(ByVal sender As System.Object, ByVal e As System.EventArgs) _
	Handles btnHandlingAnExceptionMultipleTimes.Click
        Try
            Dim mm As New ManyMethods
            ' This method goes into four other methods!
            mm.GoIntoDeeperMethodsWithCatch()
        Catch ex As Exception
            MessageBox.Show(ex.Message & Environment.NewLine & _
                            "The method which threw me was: " & _
			 ex.TargetSite.Name & Environment.NewLine & _
                            "The method which initially threw me was: _
			 " & ex.InnerException.TargetSite.Name, _
                            Me.Text, MessageBoxButtons.OK, MessageBoxIcon.Error)
        End Try
    End Sub

	Public Sub GoIntoDeeperMethodsWithCatch()
        Try
            ' This will eventually throw an exception.
            GoIntoDeeperMethods()
        Catch ex As Exception
            MessageBox.Show("Do some specific error handling here...", _
		"Some deeper method", MessageBoxButtons.OK, MessageBoxIcon.Information)
            ' Rethrow the Exception to the form so the form can handle the user interface.
            Dim newEx As New System.Exception("I am rethrown!", ex)
            Throw newEx
        End Try
    End Sub

按钮事件处理程序调用一个方法,该方法又调用其他方法。请注意,这里的第二个方法也有一个 Try... Catch... 块。最终,您会得到一个 Exception ,它会被发送到堆栈上找到的第一个 Catch 块。在这种情况下,它将是 GoIntoDeeperMethodsWithCatch中的 Catch。但是,堆栈的更下方还有一个 Catch 块,它想通知用户出了问题。

因此,第一个 Catch 块处理 Exception ,然后抛出一个新的 Exception ,并将原始 Exception 作为其 InnerExceptionException 对象的 InnerException 属性可以包含另一个 Exception。所以这里发生的是 GoIntoDeeperMethodsWithCatch 方法处理原始 Exception ,然后将一个新的 Exception 抛出到 Form。然后 Form 将拥有关于新 Exception 和原始 Exception (它包含在新 Exception InnerException 属性中)的信息。使用 Exception 对象的 InnerException 属性时要小心。如果未提供 InnerException ,上述代码将导致 NullReferenceException。上述代码的替代方法是在 GoIntoDeeperMethodsWithCatch 方法中简单地 Catch ex as Exception,然后 Throw ex。在这种情况下,原始 Exception 将被抛出到上面的 Form Class。因为在这种情况下,您的 Exception 将没有 InnerException ,您还需要编辑按钮事件处理程序中的 Exception 处理。基本上,只有当您想向原始 Exception 添加一些信息时,您才会创建一个新的 Exception。例如,第一个 Exception 已经被处理了。

嵌套的 Try... Catch... Finally...

有时,您想在一段代码中进行多个错误处理。在这种情况下,可以使用嵌套的 Try... Catch... Finally... 块。

Private Sub btnNestedTryCatch_Click(ByVal sender As System.Object, _
	ByVal e As System.EventArgs) Handles btnNestedTryCatch.Click
        Try
            ' Some code here...
            Try
                ' Some more code here...
                Dim table As New DataTable
                Try
                    ' Some more code here...
                    ' Oops! An error occurs!
                    Throw New System.Exception("I come from the third nested try.")
                    ' No Catch here, only a finally.
                Finally
                    ' This code will always execute.
                    table.Dispose()
                End Try
            Catch ex As Exception
                MessageBox.Show(ex.Message & Environment.NewLine & _
		" I was handled in the second Try... Catch... block.", _
                                Me.Text, MessageBoxButtons.OK, MessageBoxIcon.Error)
            End Try
        Catch ex As Exception
            MessageBox.Show(ex.Message & Environment.NewLine & _
		" I was handled in the first Try... Catch... block.", _
                            Me.Text, MessageBoxButtons.OK, MessageBoxIcon.Error)
        End Try
    End Sub

在此示例中,有三个嵌套的 Try... 块。第三个没有 Catch,只有一个 Finally。但这没问题,因为如果这里发生 Exception ,它将被简单地抛出到第二个 Try... 块,该块有一个 Catch 来处理 Exception
如果在第二个或第三个嵌套的 Try... 块中发生 Exception Exception 将永远不会到达第一个 Try... 块(除非它被重新抛出)。如果 Exception 发生在第一个块中,它将永远不会到达第二个和第三个 Try... 块。

只有当代码执行到第三个 Try... 块时,Finally... 才会被执行。当然,您也可以在第一个和第二个 Try... 块中添加 Finally... 块,并且您还可以添加多个 Catches(用于 SqlException、IOException等)。

另外请注意,我有两个名为 ex的变量。这意味着 ex 变量仅在 Catch 块内存在。同样,我在第二个嵌套的 Try... 块中声明了 DataTable ,这意味着它在第一个 Try... 块中不存在,因此无法在第一个 Try... 语句的 Finally... 块中 Dispose 它。

Using... End Using

如果您正在使用实现 IDisposable 接口的对象,那么在使用完对象后将其处理掉通常是明智的。上面的示例说明了可以通过使用 Try... Finally... 块来完成此操作。在 Finally... 块中,您可以调用您正在使用的对象的 Dispose 方法。但是,对于这些情况,有一种更简洁的写法。

Private Sub btnUsingEndUsing_Click(ByVal sender As System.Object, _
	ByVal e As System.EventArgs) Handles btnUsingEndUsing.Click
        Dim table1 As New DataTable
        Try
            ' Some code here...
        Finally
            table1.Dispose()
        End Try

        ' The above code actually does the same as this code!
        ' The Using... End Using block can be used with any object that
        ' implements IDisposable and makes sure that your object is disposed
        ' of, no matter what happens.
        Using table2 As New DataTable
            ' Some code here...
        End Using

        MessageBox.Show("This button does not actually do something." _
	& Environment.NewLine & "Check the source code :)", _
                        Me.Text, MessageBoxButtons.OK, MessageBoxIcon.Error)
    End Sub

Using... End Using 块会为您处理任何对象,无论发生什么。本示例仅展示了 Try... Finally... Dispose 模式的简短语法。嵌套的 Using... End Using 块也是允许的,正如我们在最后一个示例中将看到的。

总结...

这个最后的示例结合了上述一些示例,使用了 Try... Catch... Finally Using 块。这个示例确实会创建一个实际的 Exception,所以它不会是人为地从代码中抛出的。让我们来看这个实际的例子。

Private Sub btnToSumItUp_Click(ByVal sender As System.Object, _
	ByVal e As System.EventArgs) Handles btnToSumItUp.Click
        Me.Cursor = Cursors.WaitCursor
        Try
            DoSomeSqlStuff()
            ' This is an alternative block of code that does the same
            ' as DoSomeSqlStuff, but looks quite differently.
            ' AlternativeDoSomeSqlStuff()
        Catch sqlEx As SqlClient.SqlException
            MessageBox.Show(sqlEx.Message, "SQL Exception", _
		MessageBoxButtons.OK, MessageBoxIcon.Error)
        Catch ex As Exception
            MessageBox.Show(ex.Message, "Unknown exception", _
		MessageBoxButtons.OK, MessageBoxIcon.Error)
        Finally
            ' Make sure the cursor is set back to default!
            Me.Cursor = Cursors.Default
        End Try
    End Sub

    Private Sub DoSomeSqlStuff()
        Using connection As New SqlClient.SqlConnection_
	("Data Source=SomeServer;Initial Catalog=SomeDB;Integrated Security=True")
            Using cmd As New SqlClient.SqlCommand_
		("SELECT * FROM SomeTable", connection)
                connection.Open()
                Try
                    Using reader As SqlClient.SqlDataReader = cmd.ExecuteReader
                        While reader.Read
                            ' Do something... This will never happen :)
                        End While
                    End Using ' SqlReader is disposed.
                Finally
                    ' Always close the connection!
                    connection.Close()
                End Try
            End Using ' SqlCommand is disposed.
        End Using ' SqlConnection is disposed.
    End Sub

在这个示例中,我的按钮事件处理程序尝试连接到数据库。在此之前,光标被变成了等待光标。

在调用的方法中,我声明了一个 Connection 对象,并为其传递了一个有效的连接字符串。Connection 对象实现了 IDisposable,所以我可以使用 Using 语句来声明它。请注意,connection 变量仅在 Using... End Using 块内存在。因此,我需要进行的任何与 connection 相关的操作都应该在 Using... End Using 块内完成。

接下来,我以与声明 Connection 对象相同的方式声明了一个 Command 对象。我将连接传递给 Command 对象的构造函数。
接下来,我尝试打开连接。使用提供的 connectionstring,这当然永远不会成功。语法是正确的,但 Data Source 和 Initial Catalog(您的 SQLServer 实例)根本不存在!因此,打开连接将失败并抛出 Exception

请注意,代码没有到达 Try... Finally... 块,连接也不会被关闭。这没关系,因为我们一开始就无法打开它。只有在连接打开后,关闭连接才变得重要,从而进入 Try... Finally... 块。代码现在将首先到达 End Using 语句,并按顺序处理(Dispose)您的 Command 对象和 Connection 对象。然后,Exception 将在一个方法层级之上,即在按钮事件处理程序中被处理。在这里,它可以被捕获为 SqlException (在本例中就是这样)或任何其他 Exception。在 Exception 被处理之后,代码将进入 Finally... 块,并将我的光标改回默认状态。

DoSomeSqlStuff 的另一种处理方式是使用以下代码,它不使用 Using... End Using 语句来处理 Connection Command 对象。

 Private Sub AlternativeDoSomeSqlStuff()
        Dim connection As New SqlClient.SqlConnection_
	("Data Source=SomeServer;Initial Catalog=SomeDB;Integrated Security=True")
        Dim cmd As New SqlClient.SqlCommand("SELECT * FROM SomeTable", connection)
        Try
            connection.Open()
            Using reader As SqlClient.SqlDataReader = cmd.ExecuteReader
                While reader.Read
                    ' Do something... This will never happen :)
                End While
            End Using ' SqlReader is disposed.
        Finally
            connection.Close()
            connection.Dispose()
            cmd.Dispose()
        End Try
    End Sub

请注意,如果应用程序无法创建新的 Connection 对象(例如,因为连接字符串在语法上不正确),代码将永远不会跳转到 Try... Finally... 块,而是直接转到按钮事件处理程序中的错误处理。如果创建 Command 对象失败,您也不会进入 Try... Finally... 块,并且您的 Connection 对象也不会被处理(Dispose)。这种情况发生的几率确实非常小,所以在这种情况下,忽略它可能很明智,或者您需要添加另一个 Try... Finally... 块,这将使您的代码冗长且难以阅读(尽管 Using... End Using 块会为这种罕见的 Exception 做好准备)。另外请注意,如果连接打开失败,它仍将在 Finally... 块中关闭。虽然这不是真正必要的,但它不会抛出 Exception ,并且避免添加另一个 Try... Finally... 块可以使您的代码更紧凑、更易读。如果您愿意,可以在关闭连接之前检查连接对象的 State 属性。

关注点

  • Try... Catch... Finally... 块仅用于处理 Exception!!!我见过太多次将实际业务逻辑放在 Try... Catch... 块中的情况。这会使您的软件依赖于 Exception。而您不希望这样!
  • 始终尝试在用户界面级别处理 Exception。当发生问题时,您希望通知用户。不要试图在底层类中这样做。这会使您的类在其他对象中可重用性降低。例如,假设您有一个类负责处理应用程序中的电子邮件发送。第一个实现电子邮件功能的窗体是公司产品窗体。当电子邮件发送失败并抛出 Exception 时,您不希望您的类提示用户产品信息无法通过电子邮件发送。因为如果您开始在其他窗体(例如销售订单窗体)中使用这个类,当您无法通过电子邮件发送产品信息(您正在发送销售订单信息!)时,那将显得有些奇怪。
  • 您可以在底层类中处理 Exception,但不要在那里向用户传达。如果您想在用户窗体以外的 Class 中处理 Exception ,请将其重新抛出到堆栈的上方(或将其作为 InnerException 传递给一个新的 Exception 对象)。考虑您将在何处、何时以及如何抛出和处理 Exception
  • 尝试实验 Exception。您可以继承自 System.Exception 类来创建自己的 Exception 类,并添加属性和/或功能。

历史

  • 2011 年 2 月 5 日:初始帖子
© . All rights reserved.