提要 本文在简要分析VB 所提供的异常处理机制的同时详细讨论如何用它所提供的异常类定制自己的异常 结构化异常处理是随的第一个版本的发行而加入到Visual Basic语言中的结构化异常处理的重要性基于下面两个原因 与以前的On Error Goto语句相比而言通过使用一种与该语言其它部分更适应的语法从而使得代码更具有可读性 比以前的On Error Goto语句功能更强而且提供了更为灵活性的控制能力 一 捕获并抛出异常 异常处理是用TryCatchFinallyEnd Try语句实现的其基本语法形式如下 Try 能够引发一个异常的代码 Catch 处理异常的代码 Finally 实现清理工作的代码(如关闭数据库连接等等) End Try Try和End Try语句都是必需的Catch和Finally语句可以共同使用于一个Try块中但是至少使用其中之一而且可以使用多个Catch语句来处理不同类型的异常如果你有多个Catch块可以对它们加以排序从最具体的异常类型到最粗略的异常类型 Try 能够引发一个异常的代码 Catch ex As ArgumentOutOfRangeException 有可能使用一个缺省值来处理一个无效参数以便使代码继续执行 Catch ex As Exception 处理任何其它类型的异常 Finally 实现清理工作的代码(如关闭数据库连接等等) End Try 你也可以在你的代码中抛出异常当你在代码中执行一些清理工作时捕获并抛出异常是很有用的这样以来一个更高层级的过程可以捕获它当你创建定制异常类型时抛出异常也很有用 为了抛出一个异常你可以编写如下形式的代码 Throw New ArgumentOutOfRangeException ArgumentOutOfRangeException告诉Throw语句要抛出什么类型的异常这个ArgumentOutOfRangeException类型仅是NET框架所提供的众多的类型之一 二 传播异常 当一个异常出现于你的代码的某处时你可以以三种方式传播它 ·什么也不做而让它自动地传播回调用栈(由系统自动处理) ·捕获并再次抛出它这可以允许你在Finally块中运行一些清理代码 ·捕获它并使用InnerException属性在另一个异常中包装该异常并且把这个新的异常抛回调用过程这个InnerException属性可以让你维持原始的异常并在一个关系更为密切的异常中存放它的信息 三 定制自己的异常 尽框架提供了许多标准异常但你也可以创建抛出和捕获你自己的定制异常一般地微软推荐你使用由NET框架所提供的标准异常然而如果你的应用程序仅使用一个标准异常不能满足需要时你可以创建一个定制异常 当你创建一个定制异常类型时你就能控制所有的异常属性你还可以把属性添加到你的定制异常类中除了把关键数据嵌入到Message属性中外这可以使你有另外一个地方来存储这些数据而且这可以使得检索关键数据非常简单而不必在Message属性外分析它们 四 定制异常示例 为了理解定制异常让我们创建一个很简单的命令行应用程序来加以具体说明 该代码包含一个非常简单的仅含有一个表Customer的MS Access数据库一点也不奇怪这个Customer表包含一组顾客记录(具有CustomerIDfirst namelast nameaddress details共四个字段)该数据库的实际内容并不重要但是在你的定制异常中实现数据存取是一种非常合适的选择 对于本应用程序来说我用VB Express IDE创建了一个新的控制台应用程序 五 DatabaseException基类 如果你想创建多个定制异常那么为这些定制异常创建一个定制基类是个不错的主意这个基类将继承自NET框架所提供的SystemException类它还包含三个构造器它们几乎可以适用于所有你的定制异常本文中示例应用程序的基类称作DatabaseException其代码显示如下 Public Class DatabaseException Inherits Exception Public Sub New() End Sub Public Sub New(ByVal message As String) MyBaseNew(message) End Sub Public Sub New(ByVal message As String ByVal inner As Exception) MyBaseNew(message inner) End Sub End Class 这三个构造器都是SystemException类提供的标准的构造器第一个允许你创建一个异常而不必使用任何参数仅需要缺省的异常属性即可第二个允许你指定一个消息串以用作你的异常的Message属性最后第三个构造器也允许你指定一个消息串但是它允许你指定一个异常作为第二个参数SystemException类有一个属性InnerException当你捕获一个异常并想把它包装到一个关系更相近的异常的内部时这个属性很有用通过设置InnerException属性你可以维持在原始异常中的所有信息 有关基类最后一点要注意的是在从SystemException类继承还是从SystemApplicationExeption类继承的问题上开发者们的意见并不一致大多数的微软老用户认为你应该从SystemApplicationException继承但是也有一些开发者认为应该从SystemException类继承我也不确定是否它们之间存在什么技术差距但是我对这两种情况均作过测试它们都能够正常工作对于本文示例应用程序我们使用了后面的思想从SystemException类中继承 六 定制异常CustomerNotFoundException 第一个定制异常类是CustomerNotFoundException当你试图在你的数据库中查找一个客户但未找到相应的匹配时你会抛出这个异常实现代码如下所示 Public Class CustomerNotFoundException Inherits DatabaseException Private m_CustomerID As Long Public ReadOnly Property CustomerID() As Long Get Return m_CustomerID End Get End Property Public Sub New(ByVal customerID As Long) MyBaseNew(Customer ID was not found) m_CustomerID = customerID End Sub End Class 这个类继承自你前面所创建的DatabaseException基类它仅包含一个构造器其参数为customerID当调用这个构造器时你把文本串Customer ID was not found传递到基类的构造器以用作Message属性你还有一个已定义的只读属性CustomerID你将使用这个CustomerID属性来存储要被作为一个参数传递的CustomerID的值它是一个只读属性因为把这个值改变为除了引发异常的值以外的值并没有什么意义 七 定制异常DatabaseUnavailableException 第二个定制异常类是DatabaseUnavailableException当你想连接到一个数据库并发生一个异常时你就抛出这个异常其实现代码如下 Public Class DatabaseUnavailableException Inherits DatabaseException Public Sub New(ByVal ex As Exception) MyBaseNew(The database is not available ex) End Sub End Class 这个类非常相似于你的CustomerNotFoundException类你不用为这个类定义任何其它属性但是它们的主要区别在于它把一个SystemException作为一个参数并且把它传递到你的基类的构造器中这个SystemException参数将成为你的DatabaseUnavailableException的InnerException属性 八 Customer类 Customer类是一个简单类这个类的构造器使用一个数据库名和一个CustomerID作为参数并且负责检索该顾客相应的数据该构造器要做的第一件事情是通过使用ConnectDB方法尝试建立到数据库的连接具体代码如下 Private Sub ConnectDB(ByVal database As String _ ByRef cn As OleDbConnection) cnConnectionString = Provider=MicrosoftJetOLEDB; _ Data Source= & _ & database & Try cnOpen() Catch ex As Exception Throw New DatabaseUnavailableException(ex) End Try End Sub 你试图在一个Try语句内打开数据库连接如果抛出任何异常的话你就会捕获它并抛出一个DatabaseUnavailableException这个异常用它的InnerException属性包装了原始的异常 如果你成功地连接到数据库那么你就试图使用GetCustomer方法来检索相应于指定CustomerID的数据详见下面的代码 Private Sub GetCustomerData(ByVal cn As OleDbConnection _ ByVal customerID As Long) Dim cmd As New OleDbCommand Dim reader As OleDbDataReader cmdConnection = cn cmdCommandText = SELECT * FROM CUSTOMER WHERE ID = _ & customerID reader = cmdExecuteReader If readerHasRows Then readerRead() m_id = readerItem(ID) m_firstname = readerItem(Firstname) m_lastName = readerItem(Lastname) m_street = readerItem(Street) m_city = readerItem(City) m_state = readerItem(State) m_zipCode = readerItem(Zip) Else Throw New CustomerNotFoundException(customerID) End If End Sub 上面的代码中你执行了一个SQL查询其中使用CustomerID来指定你想要检索哪个顾客的数据如果查询返回一个结果那么你使用从该查询返回的值设置Customer类的属性但是如果你没有得到任何值的话你将使用customerID作为构造器的参数抛出一个CustomerNotFoundException异常这个值将用于设置你的CustomerNotFoundException对象的CustomerID属性 九 运行应用程序 下面的屏幕快照显示了你的应用程序的运行情况为了运行该应用程序你可以从命令行上调用它并指定一个数据库名和CustomerID作为参数第一个屏幕快照显示了一个成功的查询(见图)第二个屏幕快照抛出一个CustomerNotFoundException异常(见图)第三个屏幕快照抛出一个DatabaseNotAvailableException异常(见图) 图成功的查询 图CustomerNotFoundException 图DatabaseUnavailableException 十 总结 定制异常类型是语言中的一个非常有力的特征NET框架提供了许多标准的异常类型对于大多数应用而言已经足够了事实上你可能会很少用到标准异常之外的东西但是特殊情况下定制自己的异常会提高你的应用程序的健壮性 |