羽生结弦责编
胡雪蕊Try以及异常在C#中是很重要的内容,很多开发人员其实并不是很了解Try和异常。在这篇文章中我将会各大家具体讲解一下Try和异常。try、catch、finally基本介绍1.trytry语句是用来进行错误处理或者清理错误的代码块。2.catchcatch代码块可以直接访问Exception对象,这个对象中包含了相关的错误信息,catch块通常用来处理错误,或者重新排除异常。3.finallyfinally代码块增加了程序的确定性,CLR会尽力去执行它。finally通常会被用来做清理任务。注意:finally代码块并不是一定执行的,在某些情况下finally块也会不执行。try语句后面必须紧跟catch代码块或者finally代码块(也可以两者都存在)。当try中的代码发生错误时,如果存在catch代码块,那么它将会被将会被执行,如果只存在finally代码块的话,他将在try代码块执行完毕后执行,如果存在catch代码块和finally代码块的话,finally代码块将在catch代码块执行完毕后执行。finally主要的作用是不管try中是否发生错误,都要执行清理代码。现在我们通过一个例子来看一下:csharpclassProgram{classCalculation{publicintDivision(intnum){try{return10/num;}catch(DivideByZeroExceptionex){Console.WriteLine(0不能作为除数);return-;}}}staticvoidMain(string[]args){Calculationcalculation=newCalculation();calculation.Division(0);Console.Read();}}这段代码中try代码块会报DivideByZeroException错误,因为我们知道除数不可能为0。但是这里不会影响程序的运行,因为报错的代码位于try中,try将这个错误捕获到后,转给了catch,catch对这个错误进行了处理。try语句块后面的catch语句块可以有零个,也可以有1个,也可以有多个。如果有多个catch语句块的话,应该遵循从小到大的顺序编写,所谓的从小到大就是,先捕获可以预见到异常例如上面例子中的DivideByZeroException异常,再捕获其他不可预见到的异常。我们把前面的代码改动一下,来看一下:csharpclassProgram{classCalculation{publicintDivision(intnum){try{return10/num;}catch(DivideByZeroExceptionex){Console.WriteLine(0不能作为除数);return-;}catch(Exceptionex){Console.WriteLine(其他异常);return-;}}}staticvoidMain(string[]args){Calculationcalculation=newCalculation();calculation.Division(0);Console.Read();}}我们在上面的代码中增加了一个catch(Exceptionex),这个异常是所有异常的父类,它可以捕获所有任意类型的异常,因此需要把它放在所有catch语句块的后面,如果将它放在所有catch语句块的前面,将会无法通过编译。4.try、catch、finally执行原理当抛出异常时,CLR会进行一个测试,判断当前是否在执行try中,并且能被catch捕获。如果是的话,抛出的错误将会传递个能兼容这个异常的catch代码块中,当catch处理完毕后将执行try、catch后面的语句,如果存在finally代码块,那么将会先执行finally代码块,再执行后面的语句。如果不是,CLR会将这个错误向上抛出给函数的调用者,并重复这个过程。注意:这里所说的能兼容这个异常的catch代码块指的是与这个异常的类型相等的类型,或者是Exception。Catch详解catch代码块指定要补货的异常类型,这个异常类型必须是Exception或者它的子类。我在前面的小节也说过,Exception捕获的是任何类型的错误,那么一定会造成在代码中滥用Exception,这里我就说一下在什么情况下需要使用到Exception:1.无论什么类型的异常,程序都可能从异常中恢复;2.需要重新抛出异常,比如不在当前代码中处理,而是上层代码中处理,或者需要记录错误日志;3.阻止出现异常时程序被终止。除了上述情况外,我们必须针对特定类型的异常,执行特定的catch处理异常,例如前面小节中,处理除数为0的DivideByZeroExceptioncatch代码块。如果代码存在多种异常的话,可以使用多个catch进行处理不同的异常,并且针对给定的异常,只有一个catch会执行。在需要多个catch的情况下,我建议将Exception这个catch作为最后一个异常,这样当异常不是已定义的某个具体异常时,最后这个异常可以捕获,防止程序被终止。我们再来看一下例子:csharpstaticvoidMain(string[]args){try{File.Delete(
d:\.txt);Directory.Delete(D:\hahaha);}catch(DirectoryNotFoundExceptionex){Console.WriteLine(目录未找到);}catch(FileNotFoundExceptionex){Console.WriteLine(文件不存在);}catch(Exceptionex){Console.WriteLine(其他异常!);}Console.ReadLine();}在这个例子中,一共有三个catch语句块,第一个是处理目录不存在异常的,第二个是处理文件不存在异常的,最后一个是用来处理其他异常的。catch特殊用法1.省略异常变量有时候我们并不需要知道异常的详情,这个时候我们就可以省略掉异常变量,代码如下:csharpcatch(DirectoryNotFoundException){Console.WriteLine(目录未找到);}2.省略异常类型与省略异常变量一样,有时候我们也不需要异常类型,这时我们就可以省略掉异常类型。当我们省略掉异常类型时,catch块将会捕获所有类型的异常。代码如下:csharpcatch{Console.WriteLine(所有异常类型);}3.过滤异常有些异常有可能是多种原因引起的,比如WebException异常,有可能是请求超时、请求地址不存在等问题引起的,但是我们只想处理超时引发的错误,这时我们只需在catch后面加上when关键字进行过滤即可,当符合过滤条件的话会执行catch中的处理语句,如果不符合将会执行后面符合异常条件的catch语句块,代码如下:csharpcatch(WebExceptionex)when(ex.Status==WebExceptionStatus.Timeout){Console.WriteLine(超时);}finallyfinally代码块在大部分情况下都会被执行的,不管try中的代码是否执行完毕,是否有异常抛出。当如下三种情况时finally将会被执行:1.执行完一个catch代码块后;2.return语句跳出try代码块或者执行离开try代码块;3.try代码块执行完毕。我刚才也说过,finally在大部分情况下都会被执行,那么在什么情况下不会被执行呢?只有程序被强行终止或者在try代码块或catch代码块中存在无线死循环的情况下,finally才不会被执行。一般情况下我们利用finally进行清理代码。我们看一下finally的例子:csharpstaticvoidMain(string[]args){StreamReaderstreamReader=File.OpenText(d:\.txt);try{if(streamReader.EndOfStream){return;}Console.WriteLine(streamReader.ReadToEnd());}finally{if(streamReader!=null){streamReader.Dispose();}}Console.ReadLine();}上面代码中我们在try中读取文件内容并输出,在finally将所占用的资源释放掉。特殊的finally有一种特殊的finally,它就是我们经常见到的using,using可以用来释放类中的非托管资源,比如数据库连接、文件处理等。这些类都实现了IDisposable接口,通过这个接口中的Dispose方法可以释放非托管资源。例如我们将前面读取文件内容的代码修改如下,同样可以实现上面finally中的效果:csharpstaticvoidMain(string[]args){using(StreamReaderstreamReader=File.OpenText(d:\.txt)){if(streamReader.EndOfStream){return;}Console.WriteLine(streamReader.ReadToEnd());Console.ReadLine();}}异常抛出1.手动抛出异常异常不仅可以被运行时抛出,用户还可以手动抛出异常,例如我们手动抛出一个文件不存在异常,代码如下:csharpstaticvoidMain(string[]args){//..morecodethrownewFileNotFoundException();//..morecode}在三元运算符中,也可以出现异常,代码如下:csharppublicintSpecificSize(inta,intb){returnab?thrownewException(ab):a+b;}2.重新抛出异常加入我们不需要在当前方法中处理异常,我们就需要重新抛出异常,只需要在catch代码块中使用throw即可,代码如下:csharpcatch(DirectoryNotFoundExceptione){throwe;}注意:当我们使用throwe抛出异常的话,调用方接收到异常的stackfrace属性将会发生改变,不会反映出原始异常,如果需要调用发接收原始异常的话,只需要throw即可。异常抛出的特殊情况1.抛出更具体的异常有时候我们需要抛出更具体的异常,例如下面的例子:csharpcatch(Exceptione){thrownewDivideByZeroException(除零异常,e);}上面代码中的这种情况就是抛出更具体的异常,这里需要注意的有两点:(1)更具体的异常要比catch的异常类型范围要小;(2)将异常变量作为参数传递给更具体的异常。2.抛出抽象异常抛出抽象异常的目的,是因为需要穿越信任边界,防止信息泄露。下面我将列出常用的异常属性:异常属性描述:StackTrace:展现从异常发生点到catch代码块所有被调用的方法Message:异常的描述信息InnerException:引发外层异常的内层异常下面是常用的异常类型:异常类型描述:ArgumentException:参数异常ArgumentNullException:参数为null异常ArgumentOutOfRangeException:数值参数超出限定范围InvalidOperationException:操作不合理,例如未打开文件就直接读取文件内容NotSupportedException:不支持操作,例如修改只读属性NotImplementedException:所调用方法未实现ObjectDisposedException:所调用的对象已被释放NullReferenceException:空指针作者简介:朱钢,笔名羽生结弦,CSDN博客专家,.NET高级开发工程师,7年一线开发经验,参与过电子政务系统和AI客服系统的开发,以及互联网招聘网站的架构设计,目前就职于北京恒创融慧科技发展有限公司,从事企业级安全监控系统的开发。