Page Options
Life Without On Error Goto Statements
Deborah Kurata
InStep Technologies, Inc
July 11, 2003
Summary: In previous versions of Visual Basic, the best practice for handling errors was put On Error Goto in every routine, but there is no On Error statement in Visual Basic .NET. This article describes how to use new Visual Basic .NET features to handle errors without using On Error Goto statements. (8 printed pages)
概述:在旧版本的Visual Basic 中,检测错误的最好方式是在每段程序中设置 On Error Goto 语句,但在VB.Net 中这个语句已经不能使用了。这篇文章正是讲述如何在VB.Net中通过 On Error Goto 以外的方法处理错误。(共8页)
Introduction
简介
This is the second in a series of articles that describe the fundamental changes in Visual Basic® and how to do today with Visual Basic .Net what you used to do in prior versions of Visual Basic. The first article covered life without control arrays. This article looks at how to implement error handling in your application without using On Error Goto statements.
这是第二次在一连串的文章中讲述假如你过去是一个 VB 程序员的话,过渡到VB.Net 之后需要经历怎样的基础性转变。The first article covered life without control arrays.而这一次我要讲讲怎样使用 On Error Goto 以外的方法处理程序错误。
There are three types of errors that can occur in your application:
在你的程序中,有三种错误会发生。
1.Anticipated errors: These are errors that your application can anticipate, such as attempting to read a file that does not exist or attempting to open a connection with an invalid connection string.
2.Unanticipated errors: These are errors that occur in your application due to unexpected conditions, such as a programming or data error.
3.Business rule violations: These could be data entry errors, such as the user entering alpha characters into a numeric field, or they could be more complex business logic issues, such as attempting to delete an order line item for an order that has already been shipped.
1、可以预测的错误:这种错误往往是可以预知的,例如试图去打开一个不存在的文件,或者试图去打开一个使用无效的 connection string 的连接。
2、不可预测的错我:这种错误在不能预见的条件下发生,例如程序和数据的错误。
3、违反业务规范的错误:这类可能是数据录入的错误,就像把一个希腊字符输入一个数值型的字段中;更或者,所犯的错误违背了业务上的某些准则,比方说试图去删除一个已经发货的订单。
The original versions of Visual Basic provided On Error Goto for catching and handling of any errors in your application. For anticipated errors, the On Error Goto could catch the error and then your code could attempt to recover. For unanticipated errors, the On Error Goto could catch the error and then your code could terminate gracefully, without the user seeing a system error message. For business rule violations, your code could raise a specific error number and then the On Error Goto statement could catch the error and display a user-friendly message.
旧版本的VB使用 On Error Goto 对付程序中的所有错误。对于可以预测的错误,On Error Goto 捕获到错误后可以通过修改代码去排除。对于不能预测的错误,On Error Goto 捕捉到后会终止你的代码继续执行。但对于违反业务规范的错误,On Error Goto 就不能很好的捕捉并可能因此造成巨大的错误后还返回一个友好的界面。
However, On Error Goto had some limits. Its goto style syntax made your routines structurally complex. And if you did not remember to exit the routine before the error handling at the bottom, it was easy to accidentally fall through the code into the error handling. It was difficult to have clean up code that ran in all cases (regardless of whether or not an error occurred).
然而,On Error Goto 的能力是有限的,但使用 goto 语句往往使你的程序变得复杂,如果你忘记了在程序的最后留出出口,这样会使程序的流程运行出乎意料之外。这时,清除所有错误的代码就显得困难。(至少要看错误是否发生了)
Visual Basic .NET has a rich set of features that provide all of the features of On Error Goto, without the limitations.
VB.Net 对On Error Goto 提供了丰富的支持后,排除了他的局限性。
Note Actually, Visual Basic .NET does support On Error Goto through the Microsoft Visual Basic .NET Compatibility library. This library allows you to retain some of the Visual Basic 6.0 features in Visual Basic .NET to simplify the migration process. Features of this library should be used only for migration.
提醒:事实上,VB.Net 的MSVB.Net 兼容库不支持 On Error Goto ,这个兼容库主要是延续某些 VB6 的特性,使对VB.Net的移植更简单。这个对象库仅仅是为了移植使用。
Catching Exceptions
捕获异常
In .NET terms, errors are no longer called errors, but rather exceptions. Anticipated errors, unanticipated errors, and business rule violations are all considered to be exceptions.
在.Net的术语中,“错误”通常被称为“异常”,无论是可预见的错误、不可预见的错误,违反业务流程的错误,都称作异常。
After writing any routine, it is always a good idea to think about the exceptions that the routine could cause (anticipated errors), any unexpected exceptions that the routine could generate (unanticipated errors), and any business rules that the routine could violate.
当程序完成后,应当养成良好的习惯,思考程序可能出现的错误(即可预见错误),还有哪些不能估计的错误(不可预见错误)或者尽可能找出程序中与业务需求原则相违背的部分。
For example, many applications use a login form or page to control access to the application and its functions. The code to validate the login is executed when the user clicks on the login button as follows:
例如,很多程序会使用一个登录窗口获得用户信息去控制软件开放的功能,登录过程在用户按下[login]按钮时引发如下代码:
Private Sub cmdLogin_Click(ByVal sender As Object, _
ByVal e As System.EventArgs) Handles cmdLogin.Click
Dim oUser As User()
Dim bValid as Boolean
oUser = New User()
bValid = oUser.ValidateLogin(txtUserName.Text, txtPassword.Text)
If bValid then
DialogResult = DialogResult.OK
End If
oUser.Dispose
oUser = Nothing
End Sub
This code creates a new instance of the User class and then calls its ValidateLogin method to access the database and validate the user-entered username and password. If the login is valid, it sets DialogResult to OK to close the login form. It then disposes of the User class instance and returns.
代码创建了一个User类的实体并调用 ValidateLogin 方法连接数据库以验证用户的身份。如果用户合法,把“OK”附给 DialogResult 并清除User类的和其实体最后返回。
There are several places in this code that an exception could occur. The line of code that creates the new instance from the User class could generate an unanticipated exception if the instance cannot be created for some reason. The ValidateLogin method could generate anticipated exceptions (such as an invalid connection string), unanticipated exceptions (such as a missing table field or stored procedure), or business rule violations (such as passing an empty user name).
这样的代码会产生一些异常。如果User 类的实体因某些意外原因不能创建。ValidateLogin 方法也可以产生可预见的错误(例如数据库连接的失败),和不可预见的错误(比方说不能找到对应的字段和存储过程),甚至业务流程错误(用户输入了一个空的用户名)
Instead of adding an On Error Goto to catch these exceptions, the exceptions can be caught using a .NET Try/Catch block. The Try/Catch syntax makes it easier to catch and process exceptions in a structured manner; hence the reason that .NET exception handling is often referred to as structured exception handling (SEH).
在.Net中使用Try/Catch 模块,代替 On Error Goto 去捕获这些异常。Try/Catch 通过一个结构体,能更容易捕获和处以异常;因此,.Net通常用异常处以结构(SEH)去处理异常。
A Try/Catch block could be added to the code as follows:
一个 Try/Catch 模块可以添加到代码中:
Private Sub cmdLogin_Click(ByVal sender As Object, _
ByVal e As System.EventArgs) Handles cmdLogin.Click
Dim oUser As User()
Dim bValid as Boolean
Try
oUser = New User()
bValid = oUser.ValidateLogin(txtUserName.Text, txtPassword.Text)
If bValid then
DialogResult = DialogResult.OK
End If
oUser.Dispose
oUser = Nothing
Catch ex As Exception
MessageBox.Show(ex.Message)
End Try
End Sub
If any exception occurs in the Try block, the Catch block picks up the exception and the code within the Catch block will execute. In this case, the catch will grab any exception, assign the exception to the ex variable, and display a message box containing the exception message. If no exception occurs, the Catch block code is ignored.
如果一个异常发生在 Try 模块中,Catch 模块可以和提取异常并执行 Catch 后面的代码。在这个例子中,Catch模块可以提取异常,并把异常附给 ex 变量,显示一条包含错误内容的信息。如果没有异常发生,Catch 模块将被略过。
If you look closely at the example above, you will notice that the code to dispose of the instance won't be executed if an exception occurs. To correct this, the code could be repeated in the Catch block, but that means duplicating code.
A better approach would be to use the optional Finally block within the Try/Catch block as follows:
如果你认真看上面的代码,你会发现如果异常发生了,实体的清除操作将不会发生。要解决这个问题,清除实体的代码又要添加到 Catch 模块中,这样会造成重复的代码。
Private Sub cmdLogin_Click(ByVal sender As Object, _
ByVal e As System.EventArgs) Handles cmdLogin.Click
Dim oUser As User()
Dim bValid as Boolean
Try
oUser = New User()
bValid = oUser.ValidateLogin(txtUserName.Text, txtPassword.Text)
If bValid then
DialogResult = DialogResult.OK
End If
Catch ex As Exception
MessageBox.Show(ex.Message)
Finally
oUser.Dispose()
oUser = Nothing
End Try
End Sub
The code in the Finally block will be executed after the Try block completes successfully, after the Catch block executes, or after any Return statements are executed in the Try or Catch blocks. Any code that needs to be executed before leaving the routine should be added to the Finally block. Notice how the declaration of the User object was done outside of the Try block. This is required if the object variable will be accessible both from the Try block and the Finally block because .NET has block-scoped variables.
Finally 模块会在 Try 模块成功后,Catch执行后,或者Reture 语句结束后启动。所有在结束程序前需要执行的事务都可以添加到 Finally 模块。然而在 Try 模块之前声明 User 对象,是应为该对象作为模块级的变量要在 Try和Finally中共同使用。
In prior version of Visual Basic, there were three types of variable scoping:
在早期的VB中,有以下三种变量作用域:
•Global-level variables were accessible to the entire application.
•Module-level variables were accessible in the code file (form, class, or module) in which they were declared.
•Local variables were accessible only in the routine in which they were declared.
•全局变量在整个程序有效。
•模块级变量在声明代码所在的文件(窗体、类、模块)。
•本地变量只在声明他们的过程中有效。
In .NET, there is a fourth type of scoping—block-level scoping. Variables declared within a block, such as a Try block or For/Next block, are only accessible within the block.
If the declaration of the User object had been inside of the Try block, the Finally block would not be able to reference the variable. So object variables that will need to be disposed in the Finally block must be declared outside of the Try block.
在 .Net 里,这属于第四种。变量在模块中被声明,就像 Try 模块和 For/Next 模块,变量就在模块中起作用。
如果 User 对象在 Try 模块中被声明,那么在 Finally 他将不能被访问。因此,对象的值就会在 Finally 模块中被垃圾收集机制去除,显然它需要在 Try 模块意外被声明。
Throwing Exceptions
丢出异常
One of the reasons that exceptions are not called errors is that the term error frequently implies a coding mistake. An exception is any violation of a routine's implicit assumptions. The .NET Framework will throw exceptions to your application if your code violates any of the .NET Framework implicit assumptions. For example, the .NET Framework assumes that a divisor will be a non-zero number. If your code attempts to divide by 0, an exception will be thrown. You can then catch these exceptions using the Try/Catch block.
异常部叫做错误的其中一个原因,是因为错误的特征通常也包含了译码的错误。异常是任何违背了程序固有的假设。只要你的代码中含有任何违反.Net 框架的固有假设的东西,.Net 框架就会丢出一个异常。例如,.Net 框架认为除数是一个非0的值。如果你的代码试图把0 当作除数,一个异常便会被丢出。你可以在 Try/Cathc 模块中捕获它。
When writing your routines, you should follow the same guidelines and throw exceptions when any implicit assumption is violated. To throw an exception, use the Throw statement and throw a new instance of the appropriate exception class. (See the online help for the list of .NET exceptions that youcan throw.)
For example, the ValidateLogin method makes the assumption that it should receive non-empty values as the parameters. If the values are empty, it should throw an ArgumentOutOfRange exception.
在你编写程序的时候,你可以根据指导性的原则,在系统的各种固有假设被违反后丢出一个异常,使用 Throw 语句丢出一个异常类。举个例子,ValidateLogin 事件假定它收到的参数种不会有空值。如果空值出现了,他就会丢出一个 ArgumentOutOfRange 异常。
Public Function ValidateLogin(ByVal sUserName As String, _
ByVal sPassword As String) As Boolean
If sUserName.length=0 OrElse sPassword.Length=0 Then
Throw New ArgumentOutOfRangeException("Username and password are required.")
End If
' Code to validate login here
Return True
End Function
This Throw statement creates a new instance of the ArgumentOutOfRangeException and defines the message text. When this statement is executed, the exception is thrown. The code following the Throw statement is not executed, but rather the .NET runtime looks for a Try/Catch block. If the current code is not in a Try block, the .NET runtime looks up the call stack to see if the code that called this method is in a Try block. If it finds a Try block, it then looks for an associated Catch block. If the .NET runtime finds an appropriate Try/Catch block, it executes the code in the Catch block. Otherwise, it displays the unhandled exception message and terminates the application.
Note As the .NET runtime looks for associated Try blocks up the call stack, it will execute any code in the associated Finally block of the Try blocks before continuing up the call stack.
这个异常语句创建了一个 ArgumentOutOfRangeException 并定义了一个消息,当这个语句被执行,异常就会丢出。Throw 后面的语句将不会被执行,但 .Net 运行时会自动寻找 Try/Catch 模块,如果当前不在一个Try 模块中,.Net 运行时查找 Call 堆栈以检查看调用自身的事件中是否存在 Catch 模块。如果 .Net 运行时发现了一个 Try/Catch 模块,执行 Catch 模块中的代码。否则,显示一个错误信息并终止程序。
提醒:当.Net 运行时从堆栈中发现 Try 模块,在程序继续运行之前,他会先把 Try 对应的 Finally 模块中的所有代码执行完毕。
In addition to throwing .NET exceptions, you may find that you want to define your own custom exceptions. In the login example, in addition to throwing the ArgumentOutOfRangeException you may want to throw a custom exception if the username is not valid and a different custom exception if the password is not valid.
另外,对于.Net 的异常,你可以定义一个自定义的异常。以登录的为例,你可以在ArgumentOutOfRangeException 以外为不合法的用户名和密码各自附上一个自定义的异常。
To create a custom exception, you can create your own exception class. To ensure that it behaves as a .NET exception, your new exception class should inherit from one of the .NET exception classes. The recommended class to use for your inheritance is the ApplicationException class. For example, the UsernameNotFoundException class would look like this:
要创建一个自定义的异常,你可以自定义一个异常类。为了确保它是一个 .Net 的异常,你的异常类应该从一个 .Net 异常类中继承而来。其中一个比较好的继承对象是
ApplicationException 类。就像下面例子中的UsernameNotFoundException类:
Public Class UsernameNotFoundException : Inherits ApplicationException
Public Sub New()
MyBase.New()
End Sub
Public Sub New(ByVal message As String)
MyBase.New(message)
End Sub
Public Sub New(ByVal message As String, ByVal innerEx As Exception)
MyBase.New(message, innerEx)
End Sub
End Class
When using inheritance, the new class automatically has all of the properties and methods of the inherited class. So, the UsernameNotFoundException class has all of the standard ApplicationException properties, such as the message and call stack.
The new class does not inherit any of the constructors of the inherited class; hence the need for this class to have its own constructors. The ApplicationException class supports three constructors:
使用继承的时候,新的类会自动从父类继承了所有的属性。因此,UsernameNotFoundException 类会拥有 ApplicationException 类的所有属性,例如消息和调用。
新类不会继承父类的参数表,因此你需要为新的类定义参数表。ApplicationException 支持以下三种参数结构。
•One with no parameters 没有参数
•One with just the message parameter 只有一个消息参数
•One with both a message and an inner exception 一个消息和一个内部的异常
The last constructor is used in the case when the code catches an exception and then re-throws it as a different exception, but wants to retain the original exception information.
最后一个参数结构是用于代码截获一个异常后再重新丢出一个异常,但又想保持原有的异常信息。
You can also add custom properties and methods to your new exception class. For example, you could add a username property to your exception class so that you could log any exceptions and include the username of the user that had the exception.
The routine can throw custom exceptions as follows:
同样地你可以为你的新异常自定义属性和事件,例如,你可以为你的异常加入一个 username 属性用来记录所有产生异常的用户的信息。
丢出自定义异常的代码如下:
Public Function ValidateLogin(ByVal sUserName As String, _
ByVal sPassword As String) As Boolean
If sUserName.length=0 OrElse sPassword.Length=0 Then
Throw New ArgumentOutOfRangeException("Username and password are required.")
End If
' Code to locate the user record here
If iUserRecordID = 0 Then
Throw New UsernameNotFoundException("Invalid username")
End If
' Code to retrieve the password from the user record here
If sPassword <> sUserRecordPassword Then
Throw New PasswordInvalidException("Invalid password")
End If
Return True
End Function
This routine then throws either a .NET exception or a custom exception if any of the routine's implicit assumptions are violated.
Notice that there is no Try/Catch block around this code. You will find that most of your methods won't need Try/Catch blocks. Rather, all of your event procedure code will be your line of defense, catching any exceptions thrown by any of the methods called by those event procedures.
这个程序中,如果出现了任何程序错误,.Net 的异常和自定义的异常会同时被丢出。
注意,上面的代码中并没有 Try/Catch 模块,你会发现你的大多数方法都不需要 Try/Catch 模块。就是说,你所有的事件程序是就是你的保护线,由事件引起的方法对有丢出的异常都会被截获。
Catching Custom Exceptions
捕获错误
The Try/Catch block code shown at the beginning of this article provided a generic exception handler using a generic exception filter—ex as Exception. This filter would catch any .NET exception or custom exception that inherited from a .NET exception. In the login example, the generic exception filter would correctly catch any exception thrown from the ValidateLogin method.
在这编文章的开头我们在Try/Catch 模块中演示了一个一般的异常提供的一个参数 –ex。通过这个参数可以捕捉所有的.Net 异常和继承.Net异常的自定义异常。在登录例子中,这个一般的异常参数可以正确地捕获从 ValidateLogin 事件丢出的异常。
There may be cases, however, when the code needs to perform different processes depending on which exception was thrown. A Try/Catch block can contain any number of Catch blocks with more explicit exception filters that can catch specific custom or .NET exceptions and perform processing for each type of exception.
可能在某些案例中,当程序需要根据被丢出异常的内容进行不同的处理时,一个 Try/Catch 模块可以包含很多个Catch模块加上不同的参数便可以获得和处理各种自定义或者.Net异常。
Private Sub cmdLogin_Click(ByVal sender As Object, _
ByVal e As System.EventArgs) Handles cmdLogin.Click
Dim oUser As User()
Dim bValid as Boolean
Try
oUser = New User()
bValid = oUser.ValidateLogin(txtUserName.Text, txtPassword.Text)
If bValid then
DialogResult = DialogResult.OK
End If
Catch ex As UsernameNotFoundException
MessageBox.Show(ex.Message)
txtUserName.Focus()
Catch ex As PasswordInvalidException
MessageBox.Show(ex.Message)
txtPassword.Focus()
Catch ex As Exception
MessageBox.Show(ex.Message)
Finally
oUser.Dispose()
oUser = Nothing
End Try
End Sub
The order of these exception filters is important. The more specific filters should always be defined before the generic filters. The most generic filter (ex as Exception) should always be the last filter to ensure that any unanticipated exception is caught.
异常的参数是很重要的,一些特别的参数通常要在一般的异常参数之前定义。一般的异常参数通常都放到最后以便能截住所有的不可预测异常。
Conclusion 结语
Exception handling in Visual Basic has changed, but it has only gotten better. You can now build structured exception handlers to catch any type of error or business rule violation. With Try/Catch/Finally and the ability to inherit your own exception classes from the .NET exceptions, we won't be missing On Error Goto!
异常处理在VB中已经起了变化,但只有变得更好。有可以组建一个异常结构去任何错误。通过Try/Catch/Finally 加上继承 .Net 异常去定义你自己的异常类,我们也许不会再想念On Error Goto了!
•Life without Control Arrays in Visual Basic .NET
•Visual Basic Language Concepts: Structured Exception-Handling
•Visual Basic Language Specification: Structured Exception-Handling Statements
•The Visual Basic Language Newsgroup: microsoft.public.dotnet.languages.vb
•Paul Vick's Blog (Language Designer for Visual Basic)
Deborah Kurata is a software developer and the best-selling author of Doing Objects with Visual Basic 6.0. She is among the highest rated speakers at software development conferences worldwide and is the co-founder of InStep Technologies, a leading software consulting and training firm.
本文地址:http://com.8s8s.com/it/it45236.htm