C# 异常处理
debug_fan 2005.8.6
参考:优化.NET异常处理,欧岩亮
欢迎指正,转载请保留全部内容!谢谢!
一、基础
在C# 里,异常处理就是C# 为处理错误情况提供的一种机制。它为每种错误情况提供了定制的处理方式,并且把标识错误的代码与处理错误的代码分离开来。
对.NET类来说,一般的异常类 System.Exception 派生于 System.Object。还有许多定义好的异常类(如:System.SystemException、System.ApplicationException等),他们又派生于 System.Exception 类。其中System.ApplicationException 类是第三方定义的异常类,如果我们要自定义异常类,那么就应派生于它。
在代码中对异常进行处理,一般要使用三个代码块:
l Try 块的代码是程序中可能出现错误的操作部分。
l Catch 块的代码是用来处理各种错误的部分(可以有多个)。必须正确排列捕获异常的catch子句,范围小的Exception放在前面的catch。即如果Exception之间存在继承关系,就应把子类的Exception放在前面的catch子句中。
l Finally 块的代码用来清理资源或执行要在try块末尾执行的其他操作(可以省略)。且无论是否产生异常,Finally块都会执行。
二、异常处理
不管程序写得再好,异常都可能会发生,而程序也必须能够处理可能出现的错误。所以我们要站在异常一定可能会发生的角度来编写异常处理程序,应对程序有可能发生的错误建立一个良好的异常处理策略。
异常产生的时候,我们想知道的是什么原因造成的错误以及错误的相关信息。我们可以根据实际情况抛出具体类型的异常,方便捕捉到异常时做出具体的处理。在编写代码过程中,可以使用系统已定义的相关异常类以及自定义的异常类来实例化并抛出我们需要的异常。如一个不可能实现的接口,我们可以抛出“System.NotSupportedExceptiion”的异常来告诉接口的调用者。
在处理异常的时候,我们应该将可处理的具体异常分别在catch 块中做出相应处理,否则程序将终止运行。针对每一种异常,以不同方式处理,避免对所有异常做出一样的处理。并且在异常产生时,给用户一个友好的提示(普通用户对异常的具体内容是不明白的,这就需要我们给出相关的简要信息和解决方案,或则告之联系管理员等。),并在可能的情况下给用户提供可能的选择(终止,重试,忽略),让用户来决定程序的运行方向。同时,要将异常做日志记录。但不是所有异常都是必须记录的,比如一些可预料并且能让程序解决的错误我们就不需要记录它。
记录异常我们可以采取如下一些方式:
l 在文件中记录异常。便于技术人员查看所发生的异常,从而日后对程序进行改进。
l 在数据库中记录异常。数据库支持查询,这样在后期就能够对异常进行分类查询等操作,便于查看与管理。
l 在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.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 中,实现全局异常处理,但它只能处理主线程中未捕获的异常。在多线程异常处理时,工作线程/辅线程中产生异常,可以把它转给主线程来完成异常处理,线程之间不通知是无法直接捕捉异常的。如果没有去处理工作线程/辅线程中产生的异常,该异常将会消失掉。