Java与C#程序设计赵志崑中国科学院研究生院信息科学与工程学院zhaozk@gscas.ac.cn为什么程序需要出错处理•错误的产生是无法避免的–正常的程序运行时必然要与外界交互。–外部环境是程序无法完全控制的,有可能出现不正常的情况,如:•要访问的文件不存在,或者格式错误•脆弱的网络连接,导致数据传输中断•使用无效的数组下标–由人写出的代码中包含bug。•出现错误后,好的程序应该能做到以下几点:–通知用户发生了错误–允许用户保存当前的工作–允许用户顺利终止程序容易出错的问题•一个好的程序应该把所有可能出错的情况都处理到。•通常,程序中出错的情况有以下几类:–环境错误:•输入错误:如用户输入错误、文件读入错误。•设备错误:如打印机卡纸。•物理限制:硬盘空间用尽,内存耗尽。–代码错误:•无效的数组下标。•使用空引用访问对象。•各自的特点:–环境错误是无法避免的,需要程序中做出特殊处理。–代码错误可以改正,但一般难以查找。异常的概念•异常实际上是程序中错误导致中断了正常的指令流的一种事件。正常程序流程openTheFile;determineitssize;allocatememory;readthefileintomemory;closeTheFile;传统的错误处理方式openTheFile;if(theFilesOpen){determineitssize;if(gotTheFileLength){allocatememory;if(gotEnoughMemory){readthefileintomemory;if(readFailed)errorCode=-1;}elseerrorCode=-2;}elseerrorCode=-3;closeTheFile;if(closeFailed)errorCode=-4;}elseerrorCode=-5;用异常的方式来处理错误try{openTheFile;determineitssize;allocatememory;readthefileintomemory;closeTheFile;}catch(fileopenFailed){dosomething;}catch(sizeDetermineFailed){dosomething;}catch(memoryAllocateFailed){dosomething;}catch(readFailed){dosomething;}catch(fileCloseFailed){dosomething;}异常方式的优点•传统方法将大部分精力花在出错处理上了;而异常方式则可以先写出正常流程,再添加错误处理程序。•传统方法将错误处理和正常处理流程混在一起,造成程序可读性差;而异常方式则是互相独立的,程序可读性好。•传统方法都是使用分支语句来处理错误;而异常方式则按错误类型和错误差别分组。•传统方法出错返回信息量太少,通常只有一个数字作为错误代号;而异常方式返回的是一个对象,可以传递各种信息。•传统方法只把能够想到的错误考虑到,对以外的情况无法处理;而异常方式则能够部分地处理这些情况,如异常方式中可以定义不管发生何种情况一定会执行的代码。捕获异常•捕获异常使用try{}catch(){}语句。•异常在C#中实际上是一个对象。Test1捕获文件读写异常:try{StreamReaderfin=newStreamReader(Students.txt);stringline=fin.ReadLine();Console.WriteLine(line);fin.Close();}catch(FileNotFoundExceptione){//StreamReader(Students.txt)产生Console.WriteLine(e);}catch(IOExceptione)//fin.ReadLine()产生{Console.WriteLine(e);}try语句说明try语句格式:try{//接受监视的程序块}catch(异常种类异常标识符){//处理异常;}……catch(异常种类异常标识符){//处理异常;}•说明:–只要try块中的任意代码抛出一个属于catch子句所声明类的异常,那么:•跳过try块中的剩余代码;•执行相应catch子句中的异常处理代码。–如果try块中没有任何代码抛出一个异常,那么程序就会跳过所有catch子句。–如果try块中某一代码抛出一个异常,但不属于任何catch子句所声明的异常类,那么try语句所在的方法将立即退出。–catch子句能够捕获子类型的异常。将Test1中两个catch语句调换顺序。–catch子句不能捕获已经处理过的异常。异常的处理原则•如何知道代码中会产生哪些异常–对于代码中没有处理的异常,编译时会报错。–查看帮助文档,使用一个方法时,查看方法声明中的throws部分。•处理异常的原则–捕获那些知道如何处理的异常。–向上级调用者传递那些不知道该如何处理的异常。•如何向上级调用者传递异常–不使用catch子句捕获这些异常。–在方法声明时使用throws关键字向上传递这些异常。–在方法外部(从方法调用者的角度)看来,就相当于调用此方法的语句会产生这些类型的异常。向上传递异常•当一个方法自己无法处理可能产生的异常时,可以将异常传递给方法的调用者。•只要在方法中没有catch的异常,自动上传给方法调用者。staticvoidMain(){try{Read();}catch(IOExceptione){Console.WriteLine(e);}}Main方法中调用了Read()方法,可能产生IOException,所以必须处理。或者自己处理,或者继续上传。Test2staticvoidRead(){StreamReaderfin=newStreamReader(Students.txt);stringline=fin.ReadLine();Console.WriteLine(line);fin.Close();}StreamReader和fin.ReadLine都可能产生IOException,但在Read()方法中不知道如何处理,所以只能传递给Read方法的调用者(使用者)。逐级传递异常•方法可以将异常向上传递给调用者,所以可以逐级上传。昀后上传到Main函数,再传递给.Net框架。.Net框架可以昀终处理所有异常。•异常在传递过程中,一旦遇到相应的catch语句,则被捕获处理并停止传递,所以异常情况可以在适当的位置被处理。•异常只有捕获或者传递两种处理方法,二者必居其一,所以保证了所有的异常都被处理。.Net框架MainMethod1Method2catchexceptionexceptionexceptionexceptionexception抛出异常•当方法执行中检测到非正常情况,而在方法内部又处理不了时,就需要抛出异常。抛出异常实际上等于先产生出一个异常,然后将异常传递给调用者。•抛出异常使用throw语句,分为三步:–找到一个合适的异常类–创建该类的一个对象–抛出该对象(转去执行异常处理程序)staticvoidRead(stringaFileName,intaCount){……if(aCountfin.Length){thrownewEndOfStreamException(没有这么多字符。);}……}Test3staticvoidMain(){try{Read(Test3.txt,20);}catch(IOExceptione){Console.WriteLine(e);}}捕获并抛出异常•方法中能够部分处理异常时,需要先捕获异常,然后进行自己的处理,昀后再将异常传递给调用者。staticvoidRead(){try{StreamReaderfin=newStreamReader(Students.txt);stringline=fin.ReadLine();Console.WriteLine(line);fin.Close();}catch(IOExceptione){Console.WriteLine(read:+e);throwe;}}Test4staticvoidMain(){try{Read();}catch(IOExceptione){Console.WriteLine(main:+e)}}异常的种类•简化的C#异常继承关系图。ArgumentExceptionSystemExceptionExceptionIOExceptionApplicationExceptionArgumentNullExceptionArgumentOutOfRangeExceptionFileLoadExceptionFileNotFoundExceptionDirectoryNotFoundExceptionEndOfStreamException异常种类简介•任何异常类都派生自Exception类。Exception立即分为三个分支:SystemException,IOException和ApplicationException。–SystemException用于描述运行时系统的内部错误、资源耗尽错误以及程序设计错误。这种错误发生时,除了通知用户并终止程序外没有别的办法,所以不应该抛出这种类型的错误。•如果编程错误产生SystemException,应该修改程序进行改正。这类异常可以不用捕获处理。如:–错误的造型转换–越界数组访问–空引用访问–被0除–产生IOException的原因是正确的程序遇到了意外的情况,必须进行处理。如:–打开不存在的文件–打开错误的URL–ApplicationException用于自定义异常。–IOException分支是编写程序时处理的重点。各类异常的处理•各类异常的处理方法。ExceptionSystemExceptionApplicationExceptionIOException可以捕获自己处理有时需要修改程序消除可以不做处理由虚拟机处理因为没有能力处理必须捕获或传递可以抛出用户自定义异常的基类自定义异常•当标准的异常类不足以描述出现的异常情况时,可以自己创建异常类。自定义异常一样需要捕获或传递。–创建异常类只需从Exception或其子类派生一个子类即可。–该类一般具有一个无参数构造器和一个带字符串参数构造器。publicvoidSelect(intaCount){if(aCounttotalCount){thrownewSelectingException(“无法从”+totalCount+名同学中选出+aCount+名。);}}Test5classSelectingException:ApplicationException{publicSelectingException(){}publicSelectingException(stringmsg):base(msg){}}finally块•try语句可以带有finally块,表示一定执行的程序块,不管发生不发生异常,也不管发生何种类型的异常。Test7StreamReaderfin=null;try{fin=newStreamReader(Students.txt);stringline=fin.ReadLine();Console.WriteLine(line);}catch(IOExceptione){Console.WriteLine(e);}finally{try{if(fin!=null)fin.Close();}catch(IOExceptione){Console.WriteLine(关闭文件异常);}}try语句嵌套•try块中可以嵌套try语句。–内部的异常处理完后,继续执行外部的try块。–相当于异常被内部捕获后,就不再触发外部的异常处理部分。Tes