.net中的异常处理
这几天看了.net的有关异常处理方面的一些资料,下面是我的一点体会、心得,在这
里向大家请教了,有什么意见多多提出!
异常是指程序运行时遇到的任何错误或异常行为。异常处理是指针对程序中出现的异
常情况所作的相应处理。在程序中使用异常处理代码特使得程序的健壮性、可靠性更好,
并且也更加易于维护。
首先、理解异常和所预期事件(例如到达文件末后)之间的区别是十分有必要的。如果
用一个方法来按顺序地读取一个文件,那么您是知道程序在某个时候会读取到文件末尾的
。因此,该事件在本质上来说就不是“异常”的,当然也就不能阻止应用程序继续运行下
去。然而,如果您正在试图从头到尾地读取一个文件,而操作系统发出了警告,提示检查
发现有磁盘错误,这当然是一种异常情况,而且当方法试图继续读取文件时,会影响到它
的正常流程。所预期的事件可以通过逻辑判断的方式避免发生异常,如我们可以用下面的
方式关闭数据库连接,而不用try catch 语句。
if(conn.State != ConnectionState.Closed)
conn.Close();
而对于异常则是不可避免的。
绝大部分异常还会涉及到另外一个问题:上下文环境。让我们来看一个例子。假设您
正在写一段紧耦合(cohesive)的代码——在这种代码中,一个方法负责执行一个动作——
您的代码可能会是这样的:
public void FileOperate(){
File file=OpenFile(“filename”);
While(!file.IsEOF){
//read file record
}
CloseFile();
}
private File OpenFile(string fileName){
//attempt lock and open file
}
注意,如果方法OpenFile失败了,该方法本身是无法对错误进行处理的。这是因为该
方法的惟一职责就是打开文件。当文件无法打开时,它不能判断失败是由于很严重的错误
还是一个小错误而造成的。因此,OpenFile方法无法对这种错误情况进行处理,原因在于
该方法没有处于正确的上下文环境之中。
各种语言都有相应的错误或异常处理语句。我原来用的VB即主要使用on error来处理
相应的异常。传统的错误处理方法是向调用方法返回错误代码。由调用方法负责对返回值
进行解释,并采取相应的行动。返回值可能是简单的C或C++类型,也可能是指向一个复杂
对象的指针,该对象里含有完全识别和理解该错误所必需的所有信息。调用函数以某种途
径来调用一个方法,并且检查返回值,检验被调用方法的相关成功或失败。传统的错误处
理方法最大的缺点是使用代码回调的方法基本上要在每一步都要使用if判断语句判断返回
的代码有无错误,这样使程序过于臃肿。
下面是VB6.0中使用的代码回调的方法(返回字符串)来进行错误处理的方法。
function OpenFile(filename as String) as string '返回空字符串说明没有错误
on error goto Errtrap
'attempt lock and open file
return ""
ErrTrap:
return "some error message"
end function
function FileOperate(filename as String) as string '返回空字符串说明没有错
误
if OpenFile(filename)="" then ’!!!!!!!!判断OpenFile返回的字符串执行
相应的操作
on error goto Errtrap
'do some operate here
return ""
end if
ErrTrap:
return "some error message"
end function
这只是一个简单的例子,在原来的6.0中为了是自己的程序健壮几乎每个函数都需要返回结
果字符串(上例中的OpenFile和FileOperate)而在调用函数中每调一个函数也许判断该函数
的返回字符串(if OpenFile(filename)="" then)。这样编程太麻烦了。
幸好.net framework为我们提供了良好的错误处理框架。.Net框架提供了一个统一的
错误处理平台来处理错误。异常是从 Exception 类类继承的对象。异常从发生问题的代码
区域引发,然后沿堆栈向上传递,直到应用程序处理它或程序终止。相对于使用返回代码
而言,这种方法提供了更多的优点。它使程序更简洁、编程时的异常更加难以被忽略。
下面我介绍一些.net中异常处理的一些知识,我主要使参加MSDN,目录如下
→Visual studio.net
→.net framework
→使用.net framework编程
→处理和引发异常
地址:ms-help://MS.VSCC.2003/MS.MSDNQTR.2003FEB.2052/cpguide/html/cpconhandlin
gthrowingexceptions.htm
(注:这是MSDN2003的地址)
因为在MSDN中所的已经很详细了所以我在这只列一些大纲
一、了解运行库如何管理异常
运行库为每个可执行文件创建一个异常信息表。在异常信息表中,可执行文件的每个方法
都有一个关联的异常处理信息数组(可以为空)。数组中的每一项描述一个受保护的代码
块、任何与该代码关联的异常筛选器和任何异常处理程序(Catch 语句)。此异常表非常
有效,在没有发生异常时,在处理器时间或内存使用上没有性能损失。仅在异常发生时使
用资源。
二、运行库处理异常的流程图(不好意思我花了一个但是传不上去,msdn上也有,我就
是把他所说的用流程图给画了下来)
三、了解Exception类
exception类为异常处理相关类的基类,它主要有以下及各属性message,innerexception,
helplink,stachtrace。 Exception类的两个直接派生类为systemexception和applicatio
nexception.从单词上我们就能看出这两个类的作用。
四、.net中的异常处理语句
try catch finally throw这几个语句我想大家都很熟悉了
五、创建自定义异常
如果我们想以编程方式处理一些错误条件,我们可以创建自己的用户定义的异常。.N
ET Framework 提供根本上从基类 Exception 派生的异常类层次结构。这些类中的每一个
都定义一个特定的异常,因此在很多情况下只需捕捉该异常。您也可以通过从 Applicati
onException 类派生来创建自己的异常类。
创建自己的异常时,好的编码做法是
1. 以“Exception”这个词作为用户定义的异常类名的结尾。
2. 实现异常的三个构造函数
public Exception();
public Exception(string);
public Exception(string, Exception);
六、处理异常最佳做法(虽然该主题在Msdn中有并且和我下面贴的一样但我还是要贴
出来,我感觉这些建议非常有用)
设计良好的错误处理代码块集可使程序更可靠并且不容易崩溃,因为应用程序可处理
这样的错误。下表包含有关处理异常的最佳做法的建议:
1. catch块应尽可能的处理当前上下文环境中所能处理的错误。如果catch块什么也不
处理只是回抛则可以去掉,因为CLR自动向上查找调用堆栈,直到有一个方法截获这个异常
,所以中间方法可以忽略那些它们所无法处理的异常。只要确保在设计中有某个方法截获
了这个异常就可以了。如果一个异常被抛出,而在当前调用堆栈中又没有找到一个Catch块
来处理这个异常,程序就会终止。
2. 知道何时设置 Try/Catch 块。例如,可以以编程方式检查可能发生的条件,而不使
用异常处理。在其他情况下,使用异常处理捕捉错误条件是适当的。
下面的示例使用 if 语句检查连接是否关闭。如果连接未关闭,可以使用此方法而不是引
发异常。
if(conn.State != ConnectionState.Closed)
conn.Close();
在下面的示例中,如果连接未关闭,则引发异常。
try {
conn.Close();
}
catch(InvalidOperationException ex) {
//Do something with the error or ignore it.
}
所选择的方法依赖于预计事件发生的频率。如果事件确实是异常的并且是一个错误(如意
外的文件尾),则使用异常处理比较好,因为正常情况下执行的代码更少。如果事件是例
行发生的,使用编程方法检查错误比较好。在此情况下,如果发生异常,将需要更长的时
间处理。
3. 在可潜在生成异常的代码周围使用 Try/Finally 块,并将 Catch 语句集中在一个
位置。以这种方式,Try 语句生成异常,Finally 语句关闭或释放资源,而 Catch 语句从
中心位置处理异常。
4. 始终按从最特定到最不特定的顺序对 Catch 块中的异常排序。此方法在将特定异常
传递给更常规的 Catch 块之前处理该异常。
5. 以“Exception”这个词作为异常类名的结尾。例如:
public class MyFileNotFoundException : ApplicationException {
}
6. 当创建用户定义的异常时,必须确保异常的元数据对远程执行的代码可用,包括当
异常跨应用程序域发生时。例如,假设应用程序域 A 创建应用程序域 B,后者执行引发异
常代码。应用程序域 A 若想正确捕捉和处理异常,它必须能够找到包含应用程序域 B 引
发的异常的程序集。如果包含应用程序域 B 引发的异常的程序集位于应用程序域 B 的应
用程序基下,而不是位于应用程序域 A 的应用程序基下,则应用程序域 A 将无法找到异
常,公共语言运行库将引发 FileNotFoundException。为避免此情况,可以两种方式部署
包含异常信息的程序集:
• 将程序集放在两个应用程序域共享的公共应用程序基中
- 或 -
• 如果两个应用程序域不共享一个公共应用程序基,则用强名称给包含异常信息
的程序集签名并将其部署到全局程序集缓存中。
7. 在 C# 和 C++ 的托管扩展中创建您自己的异常类时,至少使用三个公共构造函数。
有关示例,请参见使用用户定义的异常。
8. 在大多数情况下,使用预定义的异常类型。仅为编程方案定义新异常类型。引入新
异常类,使程序员能够根据异常类在代码中采取不同的操作。
9. 不要从 Exception 基类派生用户定义的异常。对于大多数应用程序,从 Applicat
ionException 类派生自定义异常。
10. 在每个异常中都包含一个本地化描述字符串。当用户看到错误信息时,该信息从引
发的异常的描述字符串派生,而不是从异常类派生。
11. 使用语法上正确的错误信息(包括结束标点符号)。在异常的描述字符串中,每个
句子都应以句号结尾。
12. 为编程访问提供 Exception 属性。仅当存在附加信息有用的编程方案时,才在异
常中包含附加信息(不包括描述字符串)。
13. 对非常常见的错误情况返回空。例如,如果没找到文件,File.Open 返回空;但如
果文件被锁定,则引发异常。
14. 类的设计应使在正常使用中从不引发异常。例如,FileStream 类公开另一种确定
是否已到达文件尾的方法。这避免了在读取超过文件尾时引发的异常。下面的示例显示如
何读到文件尾。
class FileRead {
void Open() {
FileStream stream = File.Open("myfile.txt", FileMode.Open);
byte b;
// ReadByte returns -1 at EOF.
while ((b == stream.ReadByte()) != true) {
// Do something.
}
}
}
15. 如果根据对象的当前状态,属性集或方法调用不适当,则引发 InvalidOperation
Exception。
16. 如果传递无效的参数,则引发 ArgumentException 或从 ArgumentException 派生
的类。
17. 堆栈跟踪从引发异常的语句开始,到捕捉异常的 Catch 语句结束。当决定在何处
放置 Throw 语句时需考虑这一点。
18. 使用异常生成器方法。类从其实现中的不同位置引发同一异常是常见的情况。为避
免过多的代码,应使用帮助器方法创建异常并将其返回。例如:
class File {
string fileName;
public byte[] Read(int bytes) {
if (!ReadFile(handle, bytes))
throw NewFileIOException();
}
FileException NewFileIOException() {
string description = // Build localized string, including fileName.
return new FileException(description);
}
}
或者,使用异常的构造函数生成异常。这更适合全局异常类,如 ArgumentException。
19. 引发异常,而不是返回错误代码或 HRESULT。
20. 引发异常时清理中间结果。当异常从方法引发时,调用方应该能够假定没有副作用
。
七、UnhandledException事件
如果在程序中有异常没有被捕捉且不能被被调试器附加到进程时,可以对UnhandledExcep
tion事件编程。
最后希望大家的程序都能多多考虑异常情况,把异常处理好,使我们的程序固若金汤。