杂谈try-catch-finally异常处理
相关阅读:再谈异常处理trycatchfinally
1.前言
最近这段时间正开发一个店铺管理系统,这个项目定位于给中小型店铺使用的软件系统。简单的说,它处理商品的进货,销售,退货等功能。软件虽小,五脏俱全,里面涉及的技术跟大型应用软件其实差别也不大,其中有加密、数据访问、异常处理、日志、验证、ORM、依赖注入等。
本篇文章主要介绍C#语言的异常处理方面的内容,其中包含的主要内容:
•什么是异常?异常的特点?
•异常处理的基础知识。
•引发和捕捉异常的处理准则。
•避免与异常相关的性能问题的两种设计模式。
•微软企业库异常处理模块。
2.异常概述
•在应用程序遇到异常情况(如被零除情况或内存不足警告)时,就会产生异常。
•在可能引发异常的语句周围使用try块。
•try块中发生异常后,控制流会立即跳转到关联的异常处理程序(如果存在)。
•如果给定异常没有异常处理程序,则程序将停止执行,并显示一条错误消息。
•如果catch块定义了一个异常变量,则可以使用它来获取有关所发生异常的类型的更多信息。
•可能导致异常的操作通过try关键字来执行。
•异常处理程序是在异常发生时执行的代码块。在C#中,catch关键字用于定义异常处理程序。
•程序可以使用throw关键字显式地引发异常。
•异常对象包含有关错误的详细信息,比如调用堆栈的状态以及有关错误的文本说明。
•即使引发了异常,finally块中的代码也会执行,从而使程序可以释放资源。
3.异常处理基础知识
3.1.如何:使用Try/Catch块捕捉异常
将可能引发异常的代码节放在Try块中,而将处理异常的代码放在Catch块中。Catch块是一系列以关键字catch开头的语句,语句后跟异常类型和要执行的操作。
下面的代码示例使用Try/Catch块捕捉可能的异常。Main方法包含带有StreamReader语句的Try块,该语句打开名为data.txt的数据文件并从该文件写入字符串。Try块后面是Catch块,该块捕捉Try块产生的任何异常。
usingSystem;
usingSystem.IO;
usingSystem.Security.Permissions;
//Securitypermissionrequest.
[assembly:FileIOPermissionAttribute(SecurityAction.RequestMinimum,All=@"c:\data.txt")]
publicclassProcessFile{
publicstaticvoidMain(){
try{
StreamReadersr=File.OpenText("data.txt");
Console.WriteLine("Thefirstlineofthisfileis{0}",sr.ReadLine());
}
catch(Exceptione){
Console.WriteLine("Anerroroccurred:'{0}'",e);
}
}
}
3.2.如何:在Catch块中使用特定异常
发生异常时,异常沿堆栈向上传递,每个Catch块都有机会处理它。Catch语句的顺序很重要。将针对特定异常的Catch块放在常规异常Catch块的前面,否则编译器可能会发出错误。确定正确Catch块的方法是将异常的类型与Catch块中指定的异常名称进行匹配。如果没有特定的Catch块,则由可能存在的常规Catch块捕捉异常。
下面的代码示例使用try/catch块捕获InvalidCastException。该示例创建一个名为Employee的类,它带有一个属性:职员级别(Emlevel)。PromoteEmployee方法取得对象并增加职员级别。将DateTime实例传递给PromoteEmployee方法时,发生InvalidCastException。
usingSystem;
publicclassEmployee
{
//Createemployeelevelproperty.
publicintEmlevel
{
get
{
return(emlevel);
}
set
{
emlevel=value;
}
}
intemlevel;
}
publicclassEx13
{
publicstaticvoidPromoteEmployee(Objectemp)
{
//CastobjecttoEmployee.
Employeee=(Employee)emp;
//Incrementemployeelevel.
e.Emlevel=e.Emlevel+1;
}
publicstaticvoidMain()
{
try
{
Objecto=newEmployee();
DateTimenewyears=newDateTime(2001,1,1);
//Promotethenewemployee.
PromoteEmployee(o);
//PromoteDateTime;resultsinInvalidCastExceptionasnewyearsisnotanemployeeinstance.
PromoteEmployee(newyears);
}
catch(InvalidCastExceptione)
{
Console.WriteLine("ErrorpassingdatatoPromoteEmployeemethod."+e);
}
}
}
3.3.如何:显式引发异常
可以使用throw语句显式引发异常。还可以使用throw语句再次引发捕获的异常。较好的编码做法是,向再次引发的异常添加信息以在调试时提供更多信息。
下面的代码示例使用try/catch块捕获可能的FileNotFoundException。try块后面是catch块,catch块捕获FileNotFoundException,如果找不到数据文件,则向控制台写入消息。下一条语句是throw语句,该语句引发新的FileNotFoundException并向该异常添加文本信息。
usingSystem;
usingSystem.IO;
publicclassProcessFile
{
publicstaticvoidMain()
{
FileStreamfs=null;
try
{
//Opensatexttile.
fs=newFileStream(@"C:\temp\data.txt",FileMode.Open);
StreamReadersr=newStreamReader(fs);
stringline;
//Avalueisreadfromthefileandoutputtotheconsole.
line=sr.ReadLine();
Console.WriteLine(line);
}
catch(FileNotFoundExceptione)
{
Console.WriteLine("[DataFileMissing]{0}",e);
thrownewFileNotFoundException(@"data.txtnotinc:\tempdirectory]",e);
}
finally
{
if(fs!=null)
fs.Close();
}
}
}
3.4.如何:使用Finally块
异常发生时,执行将终止,并且控制交给最近的异常处理程序。这通常意味着不执行希望总是调用的代码行。有些资源清理(如关闭文件)必须总是执行,即使有异常发生。为实现这一点,可以使用Finally块。Finally块总是执行,不论是否有异常发生。
下面的代码示例使用try/catch块捕获ArgumentOutOfRangeException。Main方法创建两个数组并试图将一个数组复制到另一个数组。该操作生成ArgumentOutOfRangeException,同时错误被写入控制台。Finally块执行,不论复制操作的结果如何。
usingSystem;
classArgumentOutOfRangeExample
{
staticpublicvoidMain()
{
int[]array1={0,0};
int[]array2={0,0};
try
{
Array.Copy(array1,array2,-1);
}
catch(ArgumentOutOfRangeExceptione)
{
Console.WriteLine("Error:{0}",e);
}
finally
{
Console.WriteLine("Thisstatementisalwaysexecuted.");
}
}
}
4.异常设计准则
4.1.异常引发
•不要返回错误代码。异常是报告框架中的错误的主要手段。
•尽可能不对正常控制流使用异常。除了系统故障及可能导致争用状态的操作之外,框架设计人员还应设计一些API以便用户可以编写不引发异常的代码。例如,可以提供一种在调用成员之前检查前提条件的方法,以便用户可以编写不引发异常的代码。
•不要包含可以根据某一选项引发或不引发异常的公共成员。
•不要包含将异常作为返回值或输出参数返回的公共成员。
•考虑使用异常生成器方法。从不同的位置引发同一异常会经常发生。为了避免代码膨胀,请使用帮助器方法创建异常并初始化其属性。
•避免从finally块中显式引发异常。可以接受因调用引发异常的方法而隐式引发的异常。
4.2.异常处理
•不要通过在框架代码中捕捉非特定异常(如System.Exception、System.SystemException等)来处理错误。
•避免通过在应用程序代码中捕捉非特定异常(如System.Exception、System.SystemException等)来处理错误。某些情况下,可以在应用程序中处理错误,但这种情况极。
•如果捕捉异常是为了传输异常,则不要排除任何特殊异常。
•如果了解特定异常在给定上下文中引发的条件,请考虑捕捉这些异常。
•不要过多使用catch。通常应允许异常在调用堆栈中往上传播。
•使用try-finally并避免将try-catch用于清理代码。在书写规范的异常代码中,try-finally远比try-catch更为常用。
•捕捉并再次引发异常时,首选使用空引发。这是保留异常调用堆栈的最佳方式。
•不要使用无参数catch块来处理不符合CLS的异常(不是从System.Exception派生的异常)。支持不是从Exception派生的异常的语言可以处理这些不符合CLS的异常。
5.两种设计模式
5.1.Tester-Doer模式
Doer部分
publicclassDoer
{
publicstaticvoidProcessMessage(stringmessage)
{
if(message==null)
{
thrownewArgumentNullException("message");
}
}
}
Tester部分
publicclassTester
{
publicstaticvoidTesterDoer(ICollection<string>messages)
{
foreach(stringmessageinmessages)
{
if(message!=null)
{
Doer.ProcessMessage(message);
}
}
}
}
5.2.TryParse模式
TryParse方法类似于Parse方法,不同之处在于TryParse方法在转换失败时不引发异常。
Parse方法
publicvoidDo()
{
strings=“a”;
doubled;
try
{
d=Double.Parse(s);
}
catch(Exceptionex)
{
d=0;
}
}
TryParse方法
publicvoidTryDo()
{
strings="a";
doubled;
if(double.TryParse(s,outd)==false)
{
d=0;
}
}
6.微软企业库异常处理模块
6.1.创建自定义异常包装类
publicclassBusinessLayerException:ApplicationException
{
publicBusinessLayerException():base()
{
}
publicBusinessLayerException(stringmessage):base(message)
{
}
publicBusinessLayerException(stringmessage,Exceptionexception):
base(message,exception)
{
}
protectedBusinessLayerException(SerializationInfoinfo,StreamingContextcontext):
base(info,context)
{
}
}
6.2.配置异常处理
<addname="WrapPolicy"> <exceptionTypes> <addtype="System.Data.DBConcurrencyException,System.Data,Version=2.0.0.0,Culture=neutral,PublicKeyToken=b77a5c561934e089" postHandlingAction="ThrowNewException"name="DBConcurrencyException"> <exceptionHandlers> <addexceptionMessage="WrappedException:Arecoverableerroroccurredwhileattemptingtoaccessthedatabase." exceptionMessageResourceType=""wrapExceptionType="ExceptionHandlingQuickStart.BusinessLayer.BusinessLayerException,ExceptionHandlingQuickStart.BusinessLayer" type="Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.WrapHandler,Microsoft.Practices.EnterpriseLibrary.ExceptionHandling,Version=3.1.0.0,Culture=neutral,PublicKeyToken=b03f5f7f11d50a3a" name="WrapHandler"/> </exceptionHandlers> </add> </exceptionTypes> </add>
6.3.编写代码
publicboolProcessWithWrap()
{
try
{
this.ProcessB();
}
catch(Exceptionex)
{
//QuickStartisconfiguredsothattheWrapPolicywill
//logtheexceptionandthenrecommendarethrow.
boolrethrow=ExceptionPolicy.HandleException(ex,"WrapPolicy");
if(rethrow)
{
throw;
}
}
returntrue;
}
小结
try{//执行的代码,其中可能有异常。一旦发现异常,则立即跳到catch执行。否则不会执行catch里面的内容}
catch{//除非try里面执行代码发生了异常,否则这里的代码不会执行}
finally{//不管什么情况都会执行,包括trycatch里面用了return,可以理解为只要执行了try或者catch,就一定会执行finally}