说到c#中的try...finally...和using我想大多数人都不会陌生,这两个结构在C#中起着至关重要的作用,就是在程序抛出异常的时候仍然能够确保程序执行完某一部分代码,对于try...finally...就是在try块抛出异常时,确保仍然执行finally块中的代码,对于using就是在using块中的代码在抛出异常时,仍然执行在using上声明的对象的接口IDisposable.Dispose方法(后面会讲到这实际上还是通过try...finally...实现的)。
但是你确信你的try...finally...块在发生异常时一定会执行finally,你的using块发生异常时一定会执行Dispose方法吗?
我们首先来看看try...finally...,请先建立一个控制台项目,在粘贴如下代码:
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ExceptionTest
{
class Demo : IDisposable
{
public void Dispose()
{
Console.WriteLine("Execute Dispose!");
}
}
class Program
{
static void TryFinallyTest()
{
Demo demo = new Demo();
try
{
throw new Exception();
}
finally
{
demo.Dispose();
}
}
static void Main(string[] args)
{
TryFinallyTest();
}
}
}
这段代码很简单就是在try块发生异常后执行demo.Dispose()在控制台上输出一个字符串,但是请执行该代码,在抛出异常后点否(即不调试),结果你会发现控制台上什么都没有输出,而且程序的进程被终止了。
这说明这里的try...finally...块在发生异常后根本没有执行finally块的demo.Dispose(),这不是和前面所说的相背吗?
关闭控制台,我们再来执行一次上面的代码,这次在抛出异常时选择是,并且在VS进入调试状态后,选择停止调试。
结果我们发现控制台上显示抛出了异常,并且显示Execute Dispose!,很显然这次finally块的demo.Dispose()在抛出异常后执行了。
请注意你可以在windows的任务管理器中查看到这两种情况程序进程的存在状态有所差异,如果你在程序抛出异常后点击否,那么你会发现程序的进程就立即结束了,然而你在程序抛出异常后点击是,你会发现程序进程并没有立即结束,而是等到输出Execute Dispose!后进程才结束。这个现象很明确的表示了第一种情况没能执行finally块的原因就是程序在try中抛出异常后,还没来得及执行finally块,程序的进程立即就被操作系统终止了。同样的情况也会出现在windows项目中,请见最后的示例代码。
可见在控制台项目和windows项目中try块在抛出异常后如果没有相应的catch块来捕获异常,会导致程序进程立即终止,最终导致本来该执行的finally块的代码没被执行。那么为了让try...finally...的finally始终能被执行,那么我们就要想办法让try中的异常抛出后程序进程不要立即被终止,而是等到finally块被执行后再被终止,有一个很简单的办法可以实现这个需求,请看如下代码:
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ExceptionTest
{
class Demo : IDisposable
{
public void Dispose()
{
Console.WriteLine("Execute Dispose!");
}
}
class Program
{
static void InnerTryFinallyTest()
{
try
{
Demo demo = new Demo();
try
{
throw new Exception();
}
finally
{
demo.Dispose();
}
}
catch
{
throw;
}
}
static void Main(string[] args)
{
InnerTryFinallyTest();
}
}
}
在方法InnerTryFinallyTest中,我们将try...finally...结构放在了另一个try...catch...结构中,这样的嵌套结构起着一个很关键的作用,就是在try...finally...结构的try块中发生异常后,该异常不会立即报告给操作系统,而是先将该异常传递给外层的try...catch...结构的try块,外层的try块首先会保证其内部该执行的代码都执行完毕后再将异常传给catch块捕获(其中所谓的该执行的代码就是指内部try...finally...结构的finally块代码),最后catch块使用关键字throw将捕获的异常再原封不动的抛出给操作系统,此时程序进程立刻被操作系统终止。执行上述代码得到:
果然这次在异常抛出给操作系统前,先执行了finally块的代码。
之后我们再来看看using结构,c#中的using结构这里就不多作介绍了,不知道的朋友请查阅MSDN相关部分。你会在MSDN上查到using结构最后实际上会被编译器转换为try...finally...结构,假如有如下using结构:
using (Demo demo = new Demo())
{
throw new Exception();
}
编译器编译后得到的实际上是如下结构:
Demo demo = new Demo();
try
{
throw new Exception();
}
finally
{
if (demo != null)
((IDisposable)demo).Dispose();
}
所以在控制台项目和windows项目中,出现在try...finally...结构上的问题,同样会出现在using结构上,请见如下代码:
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ExceptionTest
{
class Demo : IDisposable
{
public void Dispose()
{
Console.WriteLine("Execute Dispose!");
}
}
class Program
{
static void UsingTest()
{
using (Demo demo = new Demo())
{
throw new Exception();
}
}
static void Main(string[] args)
{
UsingTest();
}
}
}
同样控制台上不会输出Execute Dispose!,证明using结构抛出异常后,并没有执行IDisposable.Dispose()方法,原因就是using结构内部发生异常后程序进程就被立即终止了。同理在using结构外层加上try...catch...结构就可以避免using结构抛出异常后程序进程被立即终止:
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ExceptionTest
{
class Demo : IDisposable
{
public void Dispose()
{
Console.WriteLine("Execute Dispose!");
}
}
class Program
{
static void InnerUsingTest()
{
try
{
using (Demo demo = new Demo())
{
throw new Exception();
}
}
catch
{
throw;
}
}
static void Main(string[] args)
{
InnerUsingTest();
}
}
}
这样就会在程序进程终止前执行IDisposable.Dispose()方法输出Execute Dispose!了。
上面都是对于控制台项目和windows项目展开的讨论,实际上导致上述问题的最终原因还是因为这两种项目将异常抛出给操作系统后,项目程序的进程会被操作系统终止。那么对于ASP.NET项目会不会有这个问题呢?大家都知道ASP.NET的代码都是由IIS的进程来负责执行,并且ASP.NET的代码发生了异常之后,会将异常信息输出在页面上,IIS的进程并不会被终止掉,那么是不是表示在ASP.NET中使用try...finally...结构和using结构不存在上述问题呢?我们新建一个ASP.NET web应用程序并且新建个类库文件输入如下代码:
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.IO;
namespace WebAppException
{
class Demo : IDisposable
{
public void Dispose()
{
string path = HttpContext.Current.Server.MapPath("~/") + "Log.txt";
StreamWriter sw = new StreamWriter(path, true);
sw.WriteLine("Execute Dispose!");
sw.Close();
}
}
class EClass
{
public static void TryFinallyTest()
{
Demo demo = new Demo();
try
{
throw new Exception();
}
finally
{
demo.Dispose();
}
}
public static void UsingTest()
{
using (Demo demo = new Demo())
{
throw new Exception();
}
}
}
}
由于ASP.NET的页面在抛出异常后页面显示的将会是异常信息。所以这里我们将Dispose方法输出的内容改为输出到一个文本文件上(具体项目结构请见最后的示例程序下载),然后新建一个aspx页面调用上面EClass的两个测试方法:
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace WebAppException
{
public partial class Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
EClass.TryFinallyTest();
}
}
}
执行后发现Log.txt中出现了一行Execute Dispose!文字,这说明try...finally...结构在try块抛出异常后由于IIS进程未被终止还执行了finally块,同样执行EClass.UsingTest()会得到相同的结果。可见在ASP.NET中并不存在本文所述的问题。
由此可见try...finally...结构和using结构在不同的项目中表现的行为也有所不同,具体来说就是项目本身在抛出异常后其进程是否会被操作系统终止,这里我只试验了windows项目、控制台项目和ASP.NET,关于.NET中的其他项目如果大家遇到相同的问题就可以借鉴本文所描述内容(外层嵌套try...catch...结构)来处理。
最后是本文的示例代码(代码使用VS2010编写):