开发软件一定要有足够的风险意识,认识到商业软件在各种复杂的情况下运行,必然会出现各种各样的风险和错误,这些风险和错误需要进行处理。无视风险和错误,假设一切都很和谐是很危险的思想。
程序开发中可以主动处理错误和被动处理错误,主动处理错误就是进行写代码进行功能执行前的检查,最常见也是最有效的手段就是在方法体开头处检查方法参数是否正确。主动检查程序运行速度快,而且系统更稳定,而且将风险消灭在萌芽之中,避免后期的错误大爆发,因此是优先采用的风险处理方式。[袁永福版权所有]
例如下面的代码就是主动处理错误。
public int Div( int a , int b ) { // 检查参数,若b为0,则必然会爆被0除的错误,提前处理错误。 if (b == 0) { return 0; } return a / b; } |
这段代码就是主动的检查参数是否正确,尽早制止错误的发生。
主动处理错误的方式很可能会有遗漏,C#中可以采用异常捕获机制来被动的处理错误。
在说明异常前首先得讲讲程序的调用堆栈的概念。其实基本上所有的编程语言都有调用堆栈的功能。例如在程序运行时,方法F1调用了方法F2,F2在某个瞬时又调用了方法F3,而方法F3在某个时刻调用了方法F4。则此时刻存在“F1 | F2 | F3 | F4”的调用堆栈。如下图所示,此时程序的代码呈现为一种洋葱一样的按照方法分层的结构,越靠里层就越是底层代码。
C#中的异常就是程序模块发生错误的信息,抛出异常就是程序通知系统程序发生错误了,这种异常是全局性,从抛出点开始,沿着调用堆栈,一层层的向上推动,越往上,影响面越大,若调用堆栈上某层代码能主动捕获并处理这个异常,该异常就消失掉了,系统就能正常运行,若一直没有代码能主动捕获异常,则该异常就会一直捅到了.NET框架层面。
例如在F1、F2、F3、F4构成的调用堆栈中个,如果方法F4内部主动抛出了一个异常,该异常首先抛给了方法F3,若F3不处理异常则该异常又抛给了F2,若F2还是没有处理则继续抛给了F1,若F1没有处理则直接抛给了.NET框架系统本身了。
这有点像上访,村里的农民觉得不满到找村长上访,村长不处理接着到县里上访,县里不处理则到省里上访,省里不处理则最后跑到中央上访,中央总是以简单粗暴的方式处理任何上访的。
不过凡事惊动中央是不好的,.NET框架就是.NET应用程序的中央,.NET框架本身处理没有被程序处理的异常会弹出如下的程序错误对话框
对于该对话框,若点击“继续”按钮则程序或许还能接着运行,但可能出于一种不稳定的状态;点击“退出”按钮则立即无条件的退出程序,这可能导致数据未保存。[袁永福版权所有]
一旦显示出系统级错误对话框,则该程序的稳定性可靠性是非常的差,应当尽量避免这种情况。这就需要在调用堆栈中的某些合适层面的方法中添加异常处理来应付底层方法抛出的异常。
在C#中使用“throw”关键字抛出以上,以下代码就演示了抛出异常。
public int F1() { throw new Exception("这里发生异常了"); } |
这个方法中就使用throw关键字主动抛出了一个异常。在C#中所有的异常都是某个底层方法主动抛出来的。
注意方法F1定义为需要返回一个整数数值,若方法体中没有return语句编译不通过的,但使用throw语句时可以压住这个规定。
在C#中使用“try{ }catch{ }finally{ }”结构来处理异常,以下代码就演示了如何处理异常
public void HandleException(int a, int b) { try { // 进入能捕获异常的状态 F1(); } catch (Exception ext) { // 若发生异常,在此捕获异常,执行这段代码 System.Windows.Forms.MessageBox.Show(ext.Message); } finally { // 不管有没有发生异常,本段代码都运行 Console.WriteLine("完成了处理"); } } |
在关键字“try”后面的代码块一般放置方法的功能性代码,此时系统时刻检查是否有抛出异常,若抛出异常则立即捕获它,然后跳转到关键字“catch”后面的代码块中处理异常。而无论是否发生异常,关键字“finally”后面的代码块都会执行的,这段代码一般用于本方法的善后工作,比如释放在工作过程中申请的重要资源等。Catch块和finally块是可选的,也可以写成“try{ } catch { }”或者“try{ } finally{ }”。[袁永福版权所有]
对于“try{ } catch{ }”结构,程序会捕获异常并处理掉,使得异常不再上访。
对于“try{ } finally{ }”结构,程序会感知异常,做一些处理,但不会捕获异常,异常仍然会接着上访。
使用异常处理,就能让底层方法抛出的异常被及时的处理掉而不至于一路捅到.NET框架而成为系统级的错误。因此开发程序时必须加上合适的异常处理。
由于异常是被动的处理错误,而且执行过程消耗了不少资源,因此主动防御错误永远比被动处理错误要好。在实践中可以考虑将两者都用上,做好双保险,这样程序才能可靠稳定。[袁永福版权所有]
在C#中,关键字using出了可以引用命名空间外,还可以使用using结构来实现一定的异常处理。语法结构为“using( ) { }”。例如以下代码就演示了using结构。
using (System.IO.FileStream stream = new System.IO.FileStream("c:\\a.txt", System.IO.FileMode.Create)) { stream.WriteByte((byte)2); } |
在字段代码中,using后面的园括号中打开文件创建了一个文件流对象,然后再后面的花括号的代码块中向这个文件输出一个字节,然后就没有其他代码了。
文件流是一种很重要的资源,打开后必须被关闭,否则就会资源泄露,在上面的代码中应该补上代码“stream.Close( )”来关闭文件流。但实际上面的代码是安全的,不会出现资源泄露问题,这是因为采用了using语法结构。
上述代码其功能等价于以下代码
System.IO.FileStream stream = new System.IO.FileStream("c:\\a.txt", System.IO.FileMode.Create); try { stream.WriteByte((byte)2); } finally { ( (System.IDisposable)stream).Dispose(); } |
C#编译器会将using结构翻译成一个“try{ } finally{ }”结构,并在finally块中调用文件流对象的Dispose方法,这个方法就能关闭文件流,释放所有的资源。即使其中的功能代码发生错误,抛出异常,文件流仍然能关闭来释放资源。
从这里可以看出using语法结构具有一定的异常处理功能,它没有捕获异常,但能感知异常,并进行了一些处理来减少异常带来的负面影响。
C#中的using语法结构是针对System.IDisposable接口的,System.Idisposable接口只声明了一个成员“void Dispose( )”,用于释放对象所占有的重要资源。任何实现了System.IDisposable接口的对象都能被用上using结构,比如文件流、数据库连接、网络连接等等。[袁永福版权所有]