Net Core教程

异常处理

本文主要是介绍异常处理,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
什么时候应该抛出异常

当一个类型的行动成员不能正在完整行动任务时,就应该抛出异常通知调用者。

*行动成员指类型本身或者类型实例可以执行的操作,如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会向事件日志写入一条记录,可以打开事件查看器查看
image.png

异常设置
可通过调试菜单打开异常设置窗口如图
image.png

展开Common Languages Runtime Exceptions可以查看Visual Studio能够识别的异常类型
image.png

如果勾选了异常类型的复选框,调试器就会在抛出该异常的时候中断,此时CLR不会查找任何与之匹配的catch块。
如果异常类的复选框没有勾选,调试器只有在该异常类型未得到处理时才会中断。

通过添加操作还可以添加自定义的异常类型
image.png

异常处理的性能问题
  • 非托管c++编译器:必须生成代码来跟踪哪些对象被构造成功,编译器还必须生成代码用来在一个异常被捕捉到的时候,调用每一个已经成功构造的对象的析构器。如此便会在程序中生成大量的bookkeeping代码,对代码的大小和执行时间都会造成负面影响。
  • 托管编译器:由于托管对象是在托管堆中分配的,而托管堆受到垃圾回收的监视。如果一个对象成功构造并且抛出一个异常,垃圾回收器最终会释放对象的内存。编译器无需生成任何bookkeeping代码来跟踪成功构造的对象,也无需保证析构器的调用。与托管c++相比,意味着生成的代码更少,运行要执行的代码更少,应用程序的性能更好。
这篇关于异常处理的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!