异常即程序运行时发生的错误(错误并不总是编程人员的原因,有时应用程序也会因为用户或运行代码的环境改变而发生错误;越大的程序越容易发生异常
异常处理(exceptional handling)机制分离了接收和处理错误代码,功能用于理清编程者的思绪,增强了代码可读性,方便了后期维护者的阅读和理解
(相关资料图)
异常处理(或错误处理)提供了处理程序运行时出现的任何意外或异常情况的方法,异常处理中使用 try,catch与finally三个关键字来尝试处理捕获的异常(不可能捕获所有的异常)
1)发现错误(使用try包裹可能会出现的异常代码,一般叫做异常捕获) 2)处理错误(在catch语句块处理捕获异常) 3)不论异常在2)中是否处理都会执行
注意事项:
1)发生异常后,在try语句块中异常代码之后的代码不再执行
2)finally块中的代码,无论是否发生异常都会执行;也不能写return语句(报错)
3)即使try语句块中有return语句,finally语句块也会执行
4)如果所有的异常都不匹配catch,最后依然执行finally语句块中的代码(如果有)
语法错误类的异常
这个异常绝大多数VS工具会帮我们识别出来,基本无法生成程序集;处理方法:根据错误列表的提示一一改正语法错误的代码
static void Main(string[] args){//应该与行号12 形成闭合的语句块 //1 语法错误 //类型应该是 小写 int Int num = 6; //int i=0后面应该是分号 for (int i = 0,i < 6; i++) { Console.WriteLine(i); } Console.ReadKey(); //}//不小心注释掉导致Main()方法的语句块无法闭合 //这类错误太多无法穷举,VS会有提示
语法错误的异常
处理时先按行号最小,行号相同的按列号进行处理(前面的错误会导致后面出现许多"无效"的关联错误)如上图只因一个分号隐藏了Int,关联了除X1外六个错误信息
逻辑错误错误类的异常
这个异常编译没问题,执行不报异常,就是结果不对;处理方法:可以通过调试监视变量
static void Main(string[] args){ //逻辑错误 //如本来是计算两个数的和 int n = 3; int m = 2; //不小心书将 + 写为 - int sum = n - m;//结果 1 Console.WriteLine("n + m = {0}", sum); //简化的逻辑错误代码示例 //如果出现在复杂的来回调用的方法中... //最好通过调试监视经过每个方法后变量的值 Console.ReadKey();}
以上两种类型的异常基本用不到异常处理机制
导致程序崩溃的异常
可以通过编译,只是在程序运行期间出现错误,导致程序崩溃;这个需要使用异常处理机制,使用它可以使程序跳过这个异常代码,继续执行这个异常之后的代码(已捕获了异常)
现今可以遇到的如:
1)两个操作数相除或取余时的异常;尝试除以零
2)使用如使用Convert,Parse,TryParse等进行类型转换;输入字符串的格式不正确
3)使用引用类型对象时;未将对象引用设置到对象的实例
4)其他的如操作文件或数据库时等因文件没了或没有操作权限等
static void Main(string[] args){ //编译成功,运行抛异常 //1 尝试除以零 int n = 8; int m = 0; int s = n / m; //抛异常 //2 输入字符串的格式不正确 string num = "ooo";//字母o Convert.ToInt32(num); int.Parse("ooo"); int.TryParse(num, out n); //引用类型容易出现的异常 //3 未将对象引用设置到对象的实例 //专有名词: 空指针异常 string str = null; str.ToString(); Console.ReadKey();}
代码抛出异常
程序抛出异常导致程序崩溃,无法继续执行,会给用户带来非常不好的使用体验(什么垃圾破软件....)
捕获异常并处理
异常处理的一般应用模式(三者同用方式):
try{捕获有可能出现异常的代码}当遇到异常时,try语句块中后续代码不再执行。
catch{处理捕获的异常}一般操作如记录日志,向上抛出等操作(只有发生了异常才会执行)
catch语句块中有时使用throw关键字将捕获的异常抛给"上级"去处理
finally{无论是否发生或捕获异常都会执行的代码}一般用于释放内存或资源等操作
其他的应用方式:
方式1) try → 多个catch → 一个finally
方式2) try→(1个或多个catch)多个catch的顺序问题,没有finally。
方式3) try→finally(只能有一个)没有catch
static void Main(string[] args) { Console.WriteLine("1 程序开始执行"); //这个赋值在此没有任何错误 string num = "ooo"; try { //有可能发生异常的代码 int s = 0; Console.WriteLine("2 抛出异常之前 s = {0}", s); //当程序执行到异常代码后会直接跳转到catch语句块中 s = int.Parse(num);//有可能抛出异常 Console.WriteLine("3 抛出异常之后 s = {0}", s); //除了行号11,其余基本都是"无效"代码 //两个输出语句主要是显示有异常时 //在异常代码前后的代码执行情况 } catch (Exception) //Exception处理异常的类 { //只有try中有异常时才会进入此语句块 //如果try中没有异常,直接跳转到finally语句块中 Console.WriteLine("4 程序发生异常了!!!"); //执行完后跳转到finally语句块中 } finally { Console.WriteLine("5 无论前面是否发生异常都会跳转到此语句块中"); } Console.WriteLine("6 程序结束"); Console.ReadKey(); }
抛出异常
遇到异常时,使用此方式的执行过程如上图所示,可通过调试查看整个代码执行顺序
4.1 catch中可以指定异常对象,它有3个重要属性
Source:属性表示导致异常发生的应用程序或对象的名称
Message:提供引起异常的详细信息
StackTrace:此属性提供抛异常的详细信息,并首先显示最近调用的方法
异常对象
static void Main(string[] args){ string str = null; try { //有可能发生异常的代码 Console.WriteLine(str.ToString()); } catch (Exception ex)//ex 异常对象 { //Exception 几乎可以捕获所有的异常(理论上) Console.WriteLine("ex.Source\r\n {0}", ex.Source); Console.WriteLine(""); Console.WriteLine("ex.Message\r\n {0}", ex.Message); Console.WriteLine(""); Console.WriteLine("ex.StackTrace\r\n {0}",ex.StackTrace); //1)写入日志 //2)通过throw 向上抛异常(由有处理权限或者专门的"上级"进行处理) } Console.ReadKey();}
使用其他方式中的方式2可以有针对的对某种类型的异常进行捕获处理
static void Main(string[] args){ string str = null; try { Console.WriteLine(str.ToString()); } //只为演示多catch的用法与原则 //指定捕获处理空指针异常 //如果发生的异常没有被特定的异常类捕获 //等同于没有设置异常捕获 catch (NullReferenceException nullex) { Console.WriteLine("nullex.Message\r\n {0}", nullex.Message); } //指定捕获处理 除数为0的异常 catch (DivideByZeroException divex) { Console.WriteLine("divex.Message\r\n {0}", divex.Message); } //指定捕获处理 输入的字符串不合格的异常 catch (FormatException fex) { Console.WriteLine("divex.Message\r\n {0}", fex.Message); } //可以指定许多的异常类型 //Exception 相当于其他所有异常类的"祖宗"类 //用于在上述几个异常类的最后 //Exception 不要写在第一个,否则后面针对特定异常的类就不起作用了 catch (Exception ex) { //可以将上面没有捕获到的异常捕获到 Console.WriteLine("ex.Message\r\n {0}", ex.Message); } Console.ReadKey();}
自己捕获了异常不想进行处理或没有权限进行处理,可以使用throw将异常抛给"上级"进行处理
static void Main(string[] args){ try { M1(); } catch (Exception ex) { //将M1()中的异常信息传递到了ex中 //显示异常类型的信息 Console.WriteLine(ex.Message); //显示抛异常的具体信息 Console.WriteLine(ex.InnerException.ToString()); } Console.ReadKey();}static void M1(){ string str = null; try { Console.WriteLine(str.ToString()); } //处理空指针异常(假设只可能出现此类异常) catch (NullReferenceException nullex) { //将捕获的异常信息最好先存储在日志中 //将异常抛给"上级" throw new Exception("出异常了,啦!啦啦!!啦啦啦!!!", nullex); }}
向上抛出异常
throw也可以单独使用,人为的制造异常;最近关于#鼠头鸭被禁#的事,舆情哗然,编写一个类似验证感词的程序,只要输入了敏感词就抛出异常
static void Main(string[] args){ Console.WriteLine("请输热点敏感词"); string str = Console.ReadLine(); Console.WriteLine(""); if (str.Trim() == "鼠头鸭") { throw new Exception(" (⊙_⊙) !!!"); } else { Console.WriteLine("热点敏感词 {0}", str); } Console.ReadKey();}
单独使用 throw 抛出异常
建议:最好通过逻辑判断(if-else)等语句以减少异常发生的可能性,节省性能损耗
使用异常捕获机制的益处:不会因为某段功能代码,导致整个程序崩溃(假设是计算器有加减乘除四种功能,除法因为除零抛异常,可暂时使用其他功能)提升用户体验感(类似访问的网站,某个网页已"删除",用户找不到,将页面重定向到指定的网页,不会出现304等不友好的网页)