学会使用异常
异常:指的是我们写的程序在运行时出现了错误,并且它会不断的蔓延、传播和扩散,有点像病毒一样。
异常通常由错误的代码引发,可能是用户的错误输入,可能是一方没有按照约定来传输格式,也可能是数据传输的过程中被篡改。我们会对自己认为有可能报错的代码进行 catch ,这称为捕获异常。
一旦引发了异常,这个异常将会在调用堆栈中一直向上进行传播,直到寻找到跟它匹配的 catch
语句。没有 catch 的异常会由系统提供的默认的异常处理程序进行处理,也就是你经常看到的一个突然造成调试中断并显示异常信息的对话框。
所有的异常,它都是从 Exception 派生出来的,他们都包含了详细的异常描述属性。在这里我将自定义了一个新的异常类,然后使用 throw
关键字显式引发该对象(即异常)。
1 /// <summary> 2 /// 定义新异常 3 /// </summary> 4 class MyException : Exception 5 { 6 public MyException(string msg) { } 7 } 8 9 /// <summary> 10 /// 抛出新定义的异常 11 /// </summary> 12 static void ThrowMyExcetion() 13 { 14 throw new MyException("Sorry, this is test!"); 15 }
在引发异常之后,CLR 运行时程序会检查当前语句确定它是否包含在 try
块中。 如果是的话,就会检查与该 try
块相关联的所有 catch
块,来确定它们是否能够 catch 该异常。如果该 catch
块的类型与异常或它的基类的相同(或匹配),则该 catch
块就能够捕获并处理。
1 static void Main(string[] args) 2 { 3 try 4 { 5 ThrowMyExcetion(); //直接调用抛出异常的方法 6 } 7 catch (MyException e) 8 { 9 Console.WriteLine(e); 10 } 11 12 Console.Read(); 13 }
如果引发异常的语句不在 try
块中,或者包含该语句的 try
块没有匹配的 catch
块,CLR 运行时将检查调用方法中是否有合适 try
语句和 catch
块。 运行时将在调用堆栈中继续往上搜索兼容(或匹配)的 catch
块。在找到并执行 catch
块之后,控制权将传递给 catch
块之后的下一个语句。
一个 try
语句可能包含多个 catch
块。 将执行第一个能够处理该异常的 catch
语句;任何后续的 catch
语句都将被忽略。 因此,在任何情况下都应该按照从最具体(或者派生程度最高)到最不具体这一顺序排列 catch 块。 例如:
1 static void Main(string[] args) 2 { 3 StreamWriter sw = null; 4 5 try 6 { 7 sw = new StreamWriter(@"C:ook小二和小三的故事.txt"); 8 sw.Write("You are 250."); 9 } 10 catch (FileNotFoundException e) 11 { 12 //将具体的异常放在第一位 13 Console.WriteLine(e); 14 } 15 catch (IOException e) 16 { 17 //将并不具体的放在相对后面的位置 18 Console.WriteLine(e); 19 } 20 catch (Exception e) 21 { 22 Console.WriteLine(e); 23 } 24 finally 25 { 26 if (sw != null) 27 { 28 sw.Close(); 29 } 30 } 31 32 Console.Read(); 33 }
执行 catch
块之前,运行时会检查 finally
块。 Finally
块使程序员能够清除中止的 try
块可能遗留下的任何模糊状态,或者释放任何外部资源(例如图形句柄、数据库连接或文件流),而无需等待运行时中的垃圾回收器终结这些对象。 例如:
1 static void Main(string[] args) 2 { 3 FileStream fs = null; 4 FileInfo fi = new FileInfo(@"小二和小三的故事.txt"); 5 6 try 7 { 8 fs = fi.OpenWrite(); 9 fs.WriteByte(0); 10 } 11 finally 12 { 13 //记住哦,如果你忘记 close,将会引发 IO 异常! 14 //if (fs != null) 15 //{ 16 // fs.Close(); 17 //} 18 } 19 20 try 21 { 22 fs = fi.OpenWrite(); 23 fs.WriteByte(1); 24 Console.WriteLine("OK!"); 25 } 26 catch (IOException e) 27 { 28 Console.WriteLine("Fail!"); 29 } 30 31 Console.Read(); 32 }
“Fail!”,这是因为上面注释了需要关闭文件流的语句,你可以尝试下去掉注释看看结果,记住哦,IO 操作都应该在结束时释放资源。
如果 WriteByte(0)(第9行)
引发了异常,那么在没有调用 fs.Close()
的情况下,你在第二个 try
块中尝试重新 OpenWrit() 的代码就会失败,因为此时文件会保持锁定状态。 假如你取消注释,由于会执行 finally
块(即使已引发异常),使得可以正确地关闭文件,从而避免再次引发异常。
如果在引发异常之后没有在调用堆栈上找到相匹配的 catch
块,则:
-
如果异常出现在析构函数中,则中止该析构函数并调用基类的析构函数(如果有)。
-
如果调用堆栈包含静态构造函数或静态字段初始值设定项,则会引发 TypeInitializationException,并将原始异常分配给新异常的 InnerException 属性。
-
如果到达线程的开头,将会终止线程。
C# 基础回顾系列
《C# 知识回顾 - 委托 delegate》、《C# 知识回顾 - 委托 delegate (续)》
《C# 知识回顾 - 事件入门》、《C# 知识回顾 - Event 事件》
《string 与 String,大 S 与小 S 之间没有什么不可言说的秘密》
【博主】反骨仔
【出处】http://www.cnblogs.com/liqingwen/p/6193534.html
【参考】https://docs.microsoft.com/zh-cn/dotnet/articles/csharp/programming-guide/exceptions/using-exceptions
【参考】微软官方文档