你的位置:首页 > ASP.net教程

[ASP.net教程]【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传给泛型异常类来使用,以此起到简化作用。

------未完待续,以上讲得还是些基础,接下来才会写如何去正确得使用异常处理机制---------

用可靠性换取开发效率

设计规范和最佳实践

未处理的异常

对异常进行调试

异常处理的性能问题

约束执行区域

代码协定