本文我们继续通过另一个例子来讲解在C#中如何捕捉异常并进行处理。

  首先,我们新建一个控制台应用和一个Class Library Project。如下图所示。

图1 ConsoleUI应用

图2 ExceptionLibrary类库

  在ExceptionLibrary中,我们创建了一个Demo类,该类中有TopLayerMethod、MiddleLayerMethod和GetValue三个方法。这三个方法的实现是依次调用的关系。在控制台应用的Main方法中,我们新建了一个Demo实例并将引用赋给demo变量,然后在该变量上调用GetValue方法。我们知道GetValue方法中创建的numbers数组的长度为3,但Main方法中却访问索引为3的数组元素,这导致应用抛出一个异常。那么,让我们看看异常处理构造在何处定义的呢?观察代码可以发现是在ExceptionLibrary库的Demo类的GetValue方法中定义的。我们发现catch(Exception){}语句块,该语句块中并没有进行异常处理,而仅仅是捕捉到了异常,这消除了异常变成未处理异常(未处理异常异味着程序终止运行)的可能。注意图上红色圈圈部分的代码,若移除这些代码,编译器将报错,如下图所示。

图3 编译器报并非所有路径都返回值错误

  这是因为当我们使用异常处理构造的时候,实际上应用程序在执行的时候存在两条可能的执行路径,一条是不发生异常的正常执行路径,一条是发生异常的执行路径,不论执行哪条路径都应该符合程序的语法语义,这里GetValue的返回值是int,故无论走哪条路径都要保证有int类型的返回值才行。

  我们上面的catch语句块存在的一个问题就是:我们吞噬了异常,好像异常根本没发生过一样!任何使用该应用的人都不会意识到已经发生了错误!应用带着错误的状态若无其事地继续欢快地运行!这怎么能行,我们至少应该让人知道发生了错误。对此我们做以下修正。

图4 在catch语句块中捕获并处理异常

  

图5 控制台中打印出异常信息

  修正之后我们运行程序,可以看到控制台上打印出了异常信息,这提醒我们应用发生了错误,很好,至少我们知道了本该知道的事情!但是,这还不够,我们看控制台输出的最后一句,这表明程序在抛出异常之后带着错误的状态继续运行了,导致我们得到了错误的数据。为什么会这样呢?这是因为我们在catch语句块中仅仅打印了异常信息并未做其他任何处理。我们始终必须确保一件事:如果catch语句块中不包含throw语句,那么当执行该catch语句块下方的语句时,应用程序不应带着错误的状态数据继续运行。否则,大多数情况下我们希望应用程序立即终止运行,这样至少不会使问题进一步恶化。GetValue方法在这里实际上是不能决定应用程序是否该继续运行下去的!我们可以这样想象,GetValue方法只是最下层具体执行任务的小喽啰,当不妙的事情发生时,它是拿不定主意的。这时怎么办呢?好办,我们看它的上级(调用GetValue方法的方法)是谁,把裁决权还给它的上级就行了(即把异常继续向上抛给调用栈上方的方法,从而避免底层方法继续执行产生不希望的后果)。对此,我们做进一步修正,将GetValue方法中的try...catch异常构造注释掉,然后在ConsoleUI中添加try...catch,如下图所示。

图6 将异常处理抛到ConsoleUI中处理

图7 ConsoleUI中处理异常信息能显示完整的异常堆栈

  可以看出,由于异常处理权沿着调用栈一级一级向上传,直至ConsoleApplication3的Main方法中,从而阻断了GetValue方法的继续执行。这里也向我们揭示了异常处理中一个通用准则:尽可能地将在方法调用栈底层的异常向上抛,在上层处理,能尤其是在带UI界面并采用MVC模式(在Model和Controller层发生的异常需要抛给View层以给用户提供异常信息)开发的应用程序(比如WinForm或WPF应用)中更是如此,在这些带UI界面的应用中,只有UI层能够直接和用户交互,底层使用的类库是无法和用户交流的,因此,当我们需要将异常信息显示给用户时需要将异常从调用的底层类库向上抛给最上层的UI层。在异常向上层传递的过程中,我们还能够获得从异常抛出位置到异常捕捉位置经过的所有方法记录。

  也许你会有这样的疑问,即在异常向上传递的过程中,可以捕获并进行一些处理么?比如当异常传递到TopLayerMethod方法中时,可以捕捉然后进行一些处理吗?答案是:当然可以。也许存在这样一种情况,在TopLayerMethod方法中,我们首先打开了数据库连接,然后调用了MiddleLayerMethod方法,最后关闭数据库连接,但由于调用MiddleLayerMethod时发生了异常,导致数据库连接不能关闭。

图8 异常导致TopLayerMethod方法中的数据库连接不能关闭

  这时怎么办呢?解决办法有两个。

  1. 使用try...finally块,将资源清理的工作放在finally块中。
  2. 使用try...catch...finally块。在catch块中捕捉异常并将异常信息记录到日志文件(有一种说法,记录日志文件不算异常处理,真正的异常处理必须向用户明确提示应用出现了错误,即在UI层显示异常信息),然后重新抛往上层。注意,若在catch块中除了throw语句外没有做其他处理,那么可省略该catch,也即直接使用1中的try...finally块。

图9 使用try...catch或try...catch...finally关闭数据库连接

  下面让我们看几个初学者可能犯的错误。

  1. 将上图中的catch块中的throw改为throw ex,这将导致CLR重置异常抛出的起点,不利于发现异常真实发生的位置。不建议使用。
  2. 将throw改为throw new Exception("程序出现错误")。相对于1来说,它向用户提供了更加友好的自定义信息,但是它存在和1同样的问题。不建议使用。

  此外,在开发中,我们也可能遇到这种情况,我们在catch语句中抛出了另外一个新异常,比如ArgumentException,那么我们怎么在抛出这个新异常时不丢失之前的异常信息呢?这就需要借助于inner exception了,我们可以这样使用throw new ArgumentException("输入参数有误",ex),这样就可以将之前抛出的异常作为新异常的一个属性,之后在UI层的catch语句中就可以使用两个异常的信息了。需要注意的是,在UI层处理内部异常时,可能存在异常的嵌套。为了获取所有的异常信息,我们可以使用while循环遍历内部异常,直到内部异常为空为止。若仅需获取根异常,忽略中间层异常,那么可以使用GetBaseException方法。另外,有网友反映GetBaseException有时不能获取到最原始的异常(root exception),为了解决这一问题,可以自定义一个获取root exception的方法,该方法内部采用递归调用实现。

public static class ExceptionExtensions
{
public static Exception GetOriginalException(this Exception ex)
{
if (ex.InnerException == null) return ex;
return ex.InnerException.GetOriginalException();
}
}

图10 遍历所有嵌套内部异常并打印异常堆栈信息

  最后,小结一下:在实际开发中,我们常使用类似MVC的模式开发带UI的应用,我们在Model层和Controler层发生的异常一定不能吞噬掉,应尽可能向上抛,在向上抛的过程中可以捕获该异常并做一些处理(比如将异常信息写入日志文件),或者不捕获,而是用finally块做一些资源清理工作(比如关闭打开的数据库连接),但最终还是需要抛向UI层以便及早发现程序的问题。

.NET中的异常处理机制(二)的更多相关文章

  1. .NET中的异常处理机制(一)

    1.异常处理的总体指导思想 学习C#中的异常处理机制,大概要了解以下几点: 首先,我们需要知道的事所有具体异常都是继承自System.Exception基类的. 其次,要熟悉FCL类库内置好的一些异常 ...

  2. 【C++】异常简述(一):C语言中的异常处理机制

    人的一生会遇到很多大起大落,尤其是程序员. 程序员写好的程序,论其消亡形式无非三种:无疾而终.自杀.他杀. 当然作为一名程序员,最乐意看到自己写的程序能够无疾而终,因此尽快的学习异常处理机制是非常重要 ...

  3. C++中的异常处理机制

    C++中的捕获异常机制catch参数中实参的类型不同,采取的处理方式则不相同,且与普通的函数调用还不一样,具体表现为当抛出异常throw A()或throw obj时,对象会进行一次额外的对象复制操作 ...

  4. Java中的异常处理机制《》

    异常机制已经成为判断一门编程语言是否成熟的标准,异常机制可以使程序中异常处理代码和正常业务代码分离,保证程序代码更加优雅,并提高程序健壮性. Java异常机制主要依赖于try.catch.finall ...

  5. 16、java中的异常处理机制

    异常:就是程序在运行时出现不正常情况.异常由来:问题也是现实生活中一个具体的事物,也可以通过java的类的形式进行描述.并封装成对象. 其实就是java对不正常情况进行描述后的对象体现. 对于问题的划 ...

  6. java中的异常处理机制

    java异常处理机制 1)在java语言中,通常将可能出现异常的语句放入try{}语句中,将出现错误后需要执行的语句放入到catch{}语句中,将无论是否发生异常都要执行的语句放在finally{}语 ...

  7. 深入理解C++中的异常处理机制

    异常处理 增强错误恢复能力是提高代码健壮性的最有力的途径之一,C语言中采用的错误处理方法被认为是紧耦合的,函数的使用者必须在非常靠近函数调用的地方编 写错误处理代码,这样会使得其变得笨拙和难以使用.C ...

  8. Java 中的异常处理机制

    生活中的异常:  不能够完整而顺利的完成一些工作 根据不同的异常进行相应的处理,而不会就此终端我们的生活 引出:  异常处理: 方式:  1.选择结构(逻辑判断)避免 demo:if逻辑处理异常 im ...

  9. python中的异常处理机制

    python中的异常处理 1.什么是异常 异常就是程序运行时发生错误的信号(在程序出现错误时,则会产生一个异常,若程序没有处理它,则会抛出该异常,程序的运行也随之终止),在python中,错误触发的异 ...

随机推荐

  1. 关于Selenium HTMLTestRunner 无法生成测试报告

    解决方法 1: filename = ‘E:\testresult.html’,如果是在windows环境,文件名要使用以下几种格式. ①filename = 'E:\\testresult.html ...

  2. list.contains

    list.contains(o),系统会对list中的每个元素e调用o.equals(e),方法,加入list中有n个元素,那么会调用n次o.equals(e),只要有一次o.equals(e)返回了 ...

  3. proxmox 去除订阅提示

    去掉登陆时是否订阅通知修改文件   /usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js 搜索关键字  “You do not have ...

  4. acer笔记本禁用或关闭触摸板

    acer笔记本禁用或关闭触摸板 如果启用或停用AspireOne的触摸板? 如果您希望启用或停用触摸板,请同时按下功能键(Fn)key和F7键.屏幕上会显示触摸板是否已启用或已禁用.

  5. Eclipse自动补全修改

    一.前言 之前敲代码用的是文本工具sublime,转到Eclipse之后发现补全功能特别不方便,所以想根据自己的情况进行调整,具体有两点: 输入某些语句的前几个字母就能自动提示相关的完整语句 用tab ...

  6. Docker RestApi Create mysql Container

    docker 提供了创建容器的rest api http://192.168.150.6:8888/containers/create?name=demo-mysql docker mysql容器的参 ...

  7. c语言二维数组与指针

    问题,以下输出的结果是什么,区别在于哪? void main() { ][] = { ,,,,,,,,,,, }; //输出的3个地址都一样,差别在哪? printf("\n%x" ...

  8. <<高级计算机网络>>(Advaned Computer Networks) 徐恪 徐明伟 陈文龙 马东超

    目录 第1章 计算机网络与Internet1 1.1 引言1 1.2 Internet发展历史2 1.2.1 互联网发展的主要阶段4 1.2.2 互联网在中国的发展5 1.2.3 互联网主要创新5 1 ...

  9. Laravel/Homestead storage:link -> symlink(): Protocol error

    I'm trying to run the following artisan command: php artisan storage:link I get this error: [ErrorEx ...

  10. [.NET] WeakReference的使用

    声明:本篇博客翻译自:http://tipsandtricks.runicsoft.com/CSharp/WeakReferences.html 由于水平(技术水平+英语理解能力)有限/不足,肯定会有 ...