杂谈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}