一、基础
在C# 里,异常处理就是C# 为处理错误情况提供的一种机制。它为每种错误情况提供了定制的处理方式,并且把标识错误的代码与处理错误的代码分离开来。
对.NET类来说,一般的异常类 System.Exception 派生于 System.Object。还有许多定义好的异常类(如:System.SystemException、System.ApplicationException等),他们又派生于 System.Exception 类。其中System.ApplicationException 类是第三方定义的异常类,如果我们要自定义异常类,那么就应派生于它。
在代码中对异常进行处理,一般要使用三个代码块:
Try 块的代码是程序中可能出现错误的操作部分。 Catch 块的代码是用来处理各种错误的部分(可以有多个)。必须正确排列捕获异常的catch子句,范围小的Exception放在前面的catch。即如果Exception之间存在继承关系,就应把子类的Exception放在前面的catch子句中 Finally 块的代码用来清理资源或执行要在try块末尾执行的其他操作(可以省略)。且无论是否产生异常,Finally块都会执行。
二、异常处理
不管程序写得再好,异常都可能会发生,而程序也必须能够处理可能出现的错误。所以我们要站在异常一定可能会发生的角度来编写异常处理程序,应对程序有可能发生的错误建立一个良好的异常处理策略。
异常产生的时候,我们想知道的是什么原因造成的错误以及错误的相关信息。我们可以根据实际情况抛出具体类型的异常,方便捕捉到异常时做出具体的处理。在编写代码过程中,可以使用系统已定义的相关异常类以及自定义的异常类来实例化并抛出我们需要的异常。如一个不可能实现的接口,我们可以抛出“System.NotSupportedExceptiion”的异常来告诉接口的调用者。
在处理异常的时候,我们应该将可处理的具体异常分别在catch 块中做出相应处理,否则程序将终止运行。针对每一种异常,以不同方式处理,避免对所有异常做出一样的处理。并且在异常产生时,给用户一个友好的提示(普通用户对异常的具体内容是不明白的,这就需要我们给出相关的简要信息和解决方案,或则告之联系管理员等。),并在可能的情况下给用户提供可能的选择(终止,重试,忽略),让用户来决定程序的运行方向。同时,要将异常做日志记录。但不是所有异常都是必须记录的,比如一些可预料并且能让程序解决的错误我们就不需要记录它。
记录异常我们可以采取如下一些方式:
在文件中记录异常。便于技术人员查看所发生的异常,从而日后对程序进行改进。 在数据库中记录异常。数据库支持查询,这样在后期就能够对异常进行分类查询等操作,便于查看与管理。 在Eventlog中记录异常:能够远程操作,方便系统管理员监控所有计算机的异常。
除了具体的、可预料到的异常外,还有未预料的异常。像这类异常是我们不愿意看到了,但发生了也只能暂时结束程序的运行,这里如果做好了日志就能为我们解决和调试问题带来了方便。还有,要避免使用了try-catch但没有处理异常的情况,否则就相当于给异常放行(这种情况还不如根本就不去捕获它)。
处理完异常,我们还应该注意在finally块中释放相关资源、还原相关设置信息等收尾工作。
在做异常处理的时候,最好能在应用程序所有的入口处(事件处理函数,主函数,线程入口)使用try-catch。 但是不要在程序构造函数入口处添加try-catch,因为此处产生异常,它自己并没有能力来处理,因为它还没有构造完毕,只能再向外层抛出异常。
在一般情况下使用异常机制来处理错误,能使整个程序的结构清晰、代码简单(标识错误的代码与处理错误代码分离),但我们也不能盲目使用异常。而且使用异常,可能会在一定程度上影响到程序的性能(C#中使用异常一般不影响性能)。对于一些简单的、能够提前避免的错误,我们还是应该在try块外面及早做出处理。如:
try { int x = y/z; } catch { // ... }
if(z == 0) { Console.WriteLine("除数不能为零"); // ... } try { int x = y/z; } catch { // ... }
附1:测试代码
Test01() 为一般嵌套异常处理
Test02() 为内层 catch 块中没有合适的处理程序存在的嵌套异常处理
Test03() 为在 catch 块中抛出异常的情况(当前处理不了该异常,那么把它再抛出)
using System; using System.IO;
namespace ConsoleApp { class App { // 自定义异常类 public class MyException : ApplicationException { public MyException(string message) : base(message) { }
public MyException(string message,Exception innerException) : base(message,innerException) { } }
// 实例化异常对象,并抛出异常 private static void ThrowException() { //throw new NotImplementedException(); throw new FileNotFoundException(); //throw new IOException(); }
public static void Main() { Console.Write(">>> Test01" + (char)10); Test01();
Console.Write(">>> Test02" + (char)10); Test02();
Console.Write(">>> Test03" + (char)10); Test03(); }
private static void Test01() { try { try { throw new MyException("My error."); } catch(MyException ex) { Console.WriteLine("MyException: " + ex.Message); } catch { Console.WriteLine("Unexpected error!"); } finally { Console.WriteLine(" --- inside finally ---"); }
ThrowException(); } catch(IOException ex) { Console.WriteLine("IOException: " + ex.Message); } catch(ApplicationException ex) { Console.WriteLine("ApplicationException: " + ex.Message); } catch(SystemException ex) { Console.WriteLine("SystemException: " + ex.Message); } catch { Console.WriteLine("Unexpected error!"); } finally { Console.WriteLine(" --- outside finally ---"); }
Console.ReadLine(); }
private static void Test02() { try { try { // 抛出自定义异常 MyException , // 但内层 catch 块中没有合适的处理程序存在 throw new MyException("My error."); } catch(IOException ex) { Console.WriteLine("IOException: " + ex.Message); } finally { Console.WriteLine(" --- inside finally ---"); } } catch(MyException ex) { Console.WriteLine("MyException: " + ex.Message); } catch(Exception ex) { Console.WriteLine("Exception: " + ex.Message); } finally { Console.WriteLine(" --- outside finally ---"); }
Console.ReadLine(); }
private static void Test03() { try { try { throw new IOException(); } catch(IOException ex) { // 在 catch 块中抛出异常 throw new MyException("My error." + ex.Message); } catch(Exception ex) { Console.WriteLine("Exception: " + ex.Message); } finally { Console.WriteLine(" --- inside finally ---"); } } catch(Exception ex) { Console.WriteLine("Exception: " + ex.Message); } finally { Console.WriteLine(" --- outside finally ---"); }
Console.ReadLine(); } } }
附2:全局异常处理、多线程中的异常处理
将全局异常处理函数的委托加入到 Application.ThreadException 中,实现全局异常处理,但它只能处理主线程中未捕获的异常。在多线程异常处理时,工作线程/辅线程中产生异常,可以把它转给主线程来完成异常处理。如果线程之间不通知,是无法直接捕捉异常的。若没有去处理工作线程/辅线程中产生的异常,该异常将会“消失”掉。
为什么要把异常处理都交给主线程去做呢?举个例子:在WinForm里我们使用多线程来处理界面元素,一旦有异常发生就将异常消息显示出来。那么,是直接在异常发生后就MessageBox,还是将消息交给MainUI来统一显示?试想一下,程序要是复杂点或是有多个界面采用多线程来显示界面元素,那么采用前者,我们就算知道了异常的详细信息,但可能还是很难找到究竟是哪里出了问题。而通过MainUI来显示,情况就要好很多了,尤其是还设计到其他东西的时候(如:多语言环境)。当然,这个例子只是很小的一个方面。下面就来看怎么来实现:
using System; using System.Drawing; using System.Collections; using System.ComponentModel; using System.Windows.Forms; using System.Data; using System.Threading; namespace ThreadApp { public class frmMain : System.Windows.Forms.Form { private System.Windows.Forms.Button btRun; /// /// 必需的设计器变量。 /// private System.ComponentModel.Container components = null; public delegate void WorkerThreadExceptionHandlerDelegate(Exception e); void WorkerThreadExceptionHandler(Exception e) { this.Text = "Disposed."; MainUIThreadExceptionHandler(this, new System.Threading.ThreadExceptionEventArgs(e)); }
public frmMain() { InitializeComponent(); }
/// /// 清理所有正在使用的资源。 /// protected override void Dispose( bool disposing ) { if( disposing ) { if (components != null) { components.Dispose(); } } base.Dispose( disposing ); }
#region Windows 窗体设计器生成的代码 /// /// 设计器支持所需的方法 - 不要使用代码编辑器修改 /// 此方法的内容。 /// private void InitializeComponent() { this.btRun = new System.Windows.Forms.Button(); this.SuspendLayout(); // // btRun // this.btRun.Location = new System.Drawing.Point(72, 24); this.btRun.Name = "btRun"; this.btRun.TabIndex = 0; this.btRun.Text = "Run"; this.btRun.Click += new System.EventHandler(this.btRun_Click); // // frmMain // this.AutoScaleBaseSize = new System.Drawing.Size(6, 14); this.ClientSize = new System.Drawing.Size(224, 69); this.Controls.Add(this.btRun); this.Name = "frmMain"; this.Text = "ThreadApp"; this.ResumeLayout(false);
} #endregion
/// /// 应用程序的主入口点。 /// [STAThread] static void Main() { Application.ThreadException += new ThreadExceptionEventHandler(MainUIThreadExceptionHandler); Application.Run(new frmMain()); } public static void MainUIThreadExceptionHandler(Exception e) { MainUIThreadExceptionHandler(null, new System.Threading.ThreadExceptionEventArgs(e)); }
public static void MainUIThreadExceptionHandler(object sender, ThreadExceptionEventArgs t) { MessageBox.Show(t.Exception.Message,"Exception", MessageBoxButtons.OK, MessageBoxIcon.Warning); }
private void ThrowException() { throw new NotImplementedException(); }
private void Run() { try { this.Text = "Waiting..."; //[错误]这里在2.0里是编译不通过的。因为它已经违背了我们的原则——不要跨线程操作(当前线程对界面线程的元素进行了操所) Thread.Sleep(2000); this.Text = "Throw Exception..."; ThrowException(); this.Text = "Finished"; //[错误](同上) } catch(Exception e) { // 如果涉及到多线程的互操作时, // 可以运用BeginInvoke方法来实现多线程间的互访问。 this.BeginInvoke( new WorkerThreadExceptionHandlerDelegate( WorkerThreadExceptionHandler), new object[]{e}); } }
private void btRun_Click(object sender, System.EventArgs e) { ThreadStart ts = new ThreadStart(Run); Thread t = new Thread(ts); t.Start();
//throw new NotSupportedException(); } } }