什么时候应该抛出异样
当一个类型的口头成员不能正在残缺口头工作时,就应该抛出异样告诉调用者。
* 口头成员指类型自身或者类型实例能够执行的操作,如 C# 中 StringBuilder 中定义的 Append,Insert 等
捕获异样代码构造
private void DoSomething()
{
try
{// 将可能产生异样的代码放在这里}
catch (InvalidOperationException)
{// 捕捉到 InvalidOperationException 异样,对应的解决代码放在这里}
catch (IOException)
{// 捕捉到 IOException 异样,对应的解决代码放在这里}
catch (Exception)
{
// 捕捉到除了上述之外的其它异样,对应的解决代码放在这里
// 这里是将异样抛出
throw;
}
finally
{// 这里的代码总是被执行}
// 如果 try 块没有抛出异样或者某个 catch 捕捉到异样却没有抛出就执行以下的代码,否则以下代码不执行
}
try 块
try 块中蕴含的是可能会产生异样的代码,异样复原代码应放在一个或多个 catch 块中。针对应用程序平安的复原某一种异样都须要有一个对应的 catch 块。一个 try 块至多要关联一个 catch 块或 finally 块,独自一个 try 块 C# 是不容许的,而且这样也没有意义。
* 如果一个 try 块中蕴含执行多个可能抛出同一个异样类型的操作,但不同的操作对应的复原措施不同,咱们就应该将这些操作拆分到它本人的 try 块中,以保障正确的复原状态
catch 块
catch 块蕴含的是响应一个异样须要执行的代码。一个 try 块能够关联 0 个或多个 catch 块。如果 try 块中的代码没有异样产生,CLR 永远不会执行 catch 块中的代码。线程将跳过所有 catch 块,直至 finally 块 (外面有代码的话) 中的代码。catch 关键字前面圆括号中的表达式称为捕获类型,即要捕获的异样类型。
* 应用 Visual Studio 调试 catch 块时,能够在监督窗口中增加非凡的变量名称 $exception 来查看以后抛出异样的对象
catch 块检索程序与注意事项
CLR 是自上而下检索一个匹配的 catch 块,所以编程的时候应留神将派生水平最大的异样类型放在顶部,接着是它们的基类,最初才是 System.Exception,如果程序没有放对,例如将最具体的异样类型放在了最底部的 catch 块中,C# 编译器将会产生谬误,因为这个 catch 块无奈被执行到。
一旦 try 块中的代码产生异样,而没有与之匹配的 catch 块的话,CLR 会去调用栈的更高一层搜寻与之匹配的异样类型,如果到了栈的顶部还是没有找到,就会产生一个未解决的异样。
在 catch 块的开端能够做的事件
- 从新抛出雷同的异样,向调用栈高一层的代码告诉该异样的产生
- 抛出一个不同的异样,向调用栈高一层的代码提供更加丰盛的异样信息
- 让线程从 catch 块的底部退出
前两种技术 CLR 将回溯调用栈,查找捕获类型与抛出异样的类型匹配的 catch 块并抛出一个异样。
如果抉择让线程从 catch 块的底部退出,将立刻执行蕴含在 finally 块中的代码,结束后执行紧跟在 finally 块之后的语句。如果不存在 finally 块,线程将从最初一个 catch 块之后的语句开始执行。
finally 块
finally 块中的代码是保障肯定会执行的代码且肯定要在所有 catch 块的前面,通常蕴含的是对 try 块中的口头所要求的资源清理操作。一个 try 块最多只能关联一个 finally 块。
private void ReadFile(string path)
{
FileStream fs = null;
try
{fs = new FileStream(path, FileMode.Open);
// 解决文件数据...
}
catch (IOException)
{//IOException 异样复原代码}
finally
{
// 确保文件敞开
if (fs != null)
fs.Close();}
}
上述代码,无论 try 块代码有没有产生异样,文件都肯定会被敞开。如果将敞开文件的代码放在 finally 块语句之后是不正确的,因为如果抛出异样但没有捕捉到,finally 块之后的语句将永远不会被执行,直到下一次垃圾回收才会敞开文件。
* 个别状况下 catch 块和 finally 块中的代码应只有一两行
System.Exception 类属性介绍
- 只读属性 Message:String 类型,指出抛出异样的起因
- 只读属性 Data:IDictionary 类型,代码会在抛出异样之前在该汇合中增加一个记录项
- 可读可写属性 Source:String 类型,蕴含生成异样的程序集的名称
- 只读属性 StackTrace:String 类型,蕴含抛出异样之前调用过的所有办法的名称和签名,有助于调试代码
- 只读属性 TargetSite:MethodBase 类型,蕴含抛出异样的办法
- 只读属性 HelpLink:String 类型,蕴含异样文档的 URL,不倡议应用
- 只读属性 InnerException:Exception 类型,通常值为 null,如果以后异样是在解决一个异样时抛出的,那么该属性就指出前一个异样是什么
正确应用异样类
- 善用 finally 块,罕用于显示开释对象,防止资源泄露
- 维持状态,产生不可复原的异样时回滚局部实现的操作
- 在一个线程中捕获异样,在另一个线程中从新抛出异样
- 正当的从异样中复原状态
private string SomeMothods()
{
var result = "";
var a = 1;
var b = 0;
try
{a /= b;}
catch (DivideByZeroException)
{result = "被除数不能为 0";}
return result;
}
未解决异样
异样抛出时,CLR 会在调用栈中向上查找与抛出异样对象的类型匹配的 catch 块。如果没有找到,就会产生一个未解决的异样。当 CLR 检测到过程中的任意一个线程有未解决的异样,都会终止过程。产生未解决异样表明程序遇到了未意料的状况。同时,产生未解决的异样时 windows 会向事件日志写入一条记录,能够关上事件查看器查看
异样设置
可通过调试菜单关上异样设置窗口如图
开展 Common Languages Runtime Exceptions 能够查看 Visual Studio 可能辨认的异样类型
如果勾选了异样类型的复选框,调试器就会在抛出该异样的时候中断,此时 CLR 不会查找任何与之匹配的 catch 块。
如果异样类的复选框没有勾选,调试器只有在该异样类型未失去解决时才会中断。
通过增加操作还能够增加自定义的异样类型
异样解决的性能问题
- 非托管 c ++ 编译器:必须生成代码来跟踪哪些对象被结构胜利,编译器还必须生成代码用来在一个异样被捕捉到的时候,调用每一个曾经胜利结构的对象的析构器。如此便会在程序中生成大量的 bookkeeping 代码,对代码的大小和执行工夫都会造成负面影响。
- 托管编译器:因为托管对象是在托管堆中调配的,而托管堆受到垃圾回收的监督。如果一个对象胜利结构并且抛出一个异样,垃圾回收器最终会开释对象的内存。编译器无需生成任何 bookkeeping 代码来跟踪胜利结构的对象,也无需保障析构器的调用。与托管 c ++ 相比,意味着生成的代码更少,运行要执行的代码更少,应用程序的性能更好。