C#进阶系列19 异常和状态管理
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C#进阶系列19 异常和状态管理相关的知识,希望对你有一定的参考价值。
异常就是指成员没有完成它的名称所宣示的行动。
public class Girl { public string Name { get; set; } } public class Troy{ Girl girl; public void Love() { Console.WriteLine("Troy爱上了" + girl.Name); } }
上面这段代码会有异常,因为Troy去执行Love这个函数,然而其中girl根本就没有赋值。本来Troy预期完成爱一个姑娘这个行动,结果发生了异常的事情,姑娘离开了Troy。
异常要解决的问题
很多行为(比如方法和属性)很多时候都没法返回错误代码(比如void方法,构造器,属性的获取设置),但他们仍然需要报告错误,于是异常就来解决这个问题。
也就是说异常处理机制实际上是为了返回可预知的错误代码,而不是为了去捕获未知的异常让程序不报错。(这一点非常重要)
不要去让程序吞异常,不把异常暴露出来让其继续运行,反而可能使程序做出更错误的举动。(有错就改,别藏着)
那么其实我在刚学习的时候一直有个疑问,我这个系统很多人在用啊,你如果不吞异常,那报黄页不是更6?
现在我认为这并不矛盾,如果有异常就在catch后进行异常处理还原操作,然后写日志或者用一个统一的页面去提示用户出错了,而不是把黄页去给用户看。(就像你告诉别人你得胃病了,用嘴和肢体语言表述都行,你剖开自己的肚子告诉别人你有病就是你的错了啊)
.Net的异常处理机制
.Net的异常处理机制是基于windows提供的结构化异常处理机制(Structured Exception Handing,简称SEH)构建的。
异常处理的代码就不演示了,说说三大块
- try块
- 一个try块中如果能抛出同一个异常类的操作,却要进行不同的异常恢复措施,那么应该分成两个try块。
- try和finally到一起一般是执行资源清理操作(也可以用using哦)。
- catch块
- 一个try块可以关联0个或多个catch块。
- catch后面跟着圆括号中的表达式称为捕捉类型,异常捕捉类型必须是System.Exception或者它的派生类。
- CLR自上而下搜索异常,所以要将较具体的异常放在顶部。也就是说首先写派生程度最大的异常,然后才是其基类,然后才是System.Exception或者不指定任何捕捉类型的catch块。
- 如果抛出的异常没有catch到,也就是说catch的类型没有一个与抛出的异常匹配,那么CLR就回去调用栈更高的一层搜索与异常匹配的捕捉类型。如果到了调用栈的顶部还是没有匹配到catch块,就会发生未处理的异常。而一旦找到匹配的catch块,就会执行内层所有finally块的代码,否则内层所有finally块的代码都不会执行。也就是说下面示例代码中会报异常:
static void Main(string[] args) { try { FuncA(); } finally { Console.WriteLine("主函数Finally"); } Console.Read(); } static void FuncA() { try { Object obj = new DateTime(); int a = (int)obj;//这里会报System.InvalidCastException异常 } catch(InvalidDataException)//表示不匹配,然后到调用栈的上一级也就是main函数,然而main函数中的try根本就没有catch所以更谈不上什么匹配,也就是出现了一个未处理的异常 { //这里完全不会执行 } finally {//虽然有Finally说好的,不论是否异常都会执行,然而此时上面的异常没有catch到,
//所以已经异常报错了,不会再执行到这里。此时CLR会终止进程,相较于让程序继续运行造成不可预知的结果这样更好 Console.WriteLine("函数A的Finally"); } } - catch块的末尾有以下三种处理方法:
- 重新抛出相同的异常,向调用栈高一层的代码通知该异常的发生,也就是throw;
- 抛出一个不同的异常,向调用栈高一层的代码提供更丰富的异常信息,也就是throw ex;//这里ex为新的异常对象
- 让线程从catch块底部退出,不向更高层抛异常。
- 代码可向AppDomain的FirstChanceException事件登记,这样只要AppDomain一发生异常就会收到通知,并且在CLR开始搜索任何catch块之前就会调用这些事件回调函数。
- finally块
- finally块为保证会执行的代码。
- 如果在catch内部和finally内部又抛出了异常,那么在try中的异常不会被记录,其信息将丢失。
System.Exception类
微软规定所有CLS相容的编程语言都必须抛出和捕捉派生自该类型的异常。
一般来讲也就这个类中也就三个属性要注意:
- Message指出抛出异常的原因
- InnerException如果当前异常是在处理一个异常时抛出的,那么InnerException中就是上一个异常。用公共方法GetBaseException可以遍历内部异常链表,返回最初抛出的异常。
- StackTrace包含异常抛出前调用过的所有方法的名称和签名。它返回一个从异常抛出位置到异常捕捉未知的所有方法。
抛出异常
抛异常需要考虑两个问题:
第一个是抛出什么Exception类型的异常。应该选择一个更有意义的类型。要考虑到调用栈中高处的代码,要知道那些代码如何判断一个方法失败从而执行得体的恢复代码。作者强烈建议异常的继承层次结构应该浅而宽,这样就可以尽量少的创建基类。而基类意味着把众多错误当做一个错误来处理。
第二个是向异常类型的构造器传递什么字符串消息。
自定义异常类
看起来自定义异常类很简单,只需要继承System.Exception类就OK了,然而实际上这是个很繁琐的事情。
因为从System.Exception类派生出来的所有类都应该是可序列化的,使它们能穿越AppDomain边界去写入日志或者数据库。而序列化就涉及到很多问题。
作者写了个泛型异常类去简化,我这里就不写了,实际上在格式上找个系统异常照着写就行了:
别忘了在自定义类上面加上[Serializable]特性。
作者的玩法更高端一点,自己建个泛型异常类继承Exception,然后将一些构造函数或者序列化函数写在这个类中。个性化的异常信息作为泛型变量T传给泛型异常类来使用,以此起到简化作用。
------未完待续,以上讲得还是些基础,接下来才会写如何去正确得使用异常处理机制---------
用可靠性换取开发效率
设计规范和最佳实践
未处理的异常
对异常进行调试
异常处理的性能问题
约束执行区域
代码协定
以上是关于C#进阶系列19 异常和状态管理的主要内容,如果未能解决你的问题,请参考以下文章