异常安全在Java和C#语言中的简化
C簇语言中,C++首先在引入异常。在C++标准委员会投票之时,英国代表提出了反对意见,他们说异常差不多破坏了每个程序。如下的代码片段很好地展示了异常是如何把事情搞乱的
{
T* p = new T;
// 一些处理
delete p;
}
如果处理过程引发异常抛出,那么语句“delete p;”便不会被执行,这就导致了内存泄漏。为解决这个问题,有两个提议,“智能指针”和垃圾收集。
英国人的反对意见是有道理,在C++中编写异常安全的代码,曾经困扰C++社群数年,97年至99年,终于总结了一些比较好的解决办法。至今为止,仍然是十分困难的事情。
Java和C#是一门大众化的语言,异常安全这么困难的技术,大多数程序员都会比较难以掌握,所以引入了一些简化的方式。
在Java中,引入了垃圾收集的技术,程序员不用关心内存的回收问题了。但是还是存在其他的一些资源,例如数据库连接、文件句柄、互斥体(Mutex)等等。Java中,为了简化对互斥体(Mutex)使用,引入了synchronized关键字(类似C#中的lock),使得线程的使用变得简单。如下:
synchronized (list) {
//do something
}
}
在C#中,则是更进了一步。为了解决异常安全问题,引入了lock、using两个关键字管理资源。lock与Java中的synchronized对应。
lock (obj)
{
//do something
}
上述代码在不用lock的时候,是这样写的:
try
{
//do something
}
finally
{
System.Threading.Monitor.Exit(obj);
}
而using则是一种和IDisposable配合使用的特别方式,或许是一种创新。
我们先看看IDisposable接口:
{
// Methods
void Dispose();
}
我们看看数据连接SqlConnection的实现,SqlConnection实现了IDisposable接口,SqlConnection中大约是这样实现:
{
this.Close();
}
在Dispose中调用了Close方法,释放了资源。在File相关的对象,也是以类似的方式处理的。
使用Using:
{
// do something
}
如果不是用Using,也需要保证异常安全。可以这样写,与上面的代码等价:
try
{
// do something
}
finally
{
IDisposable disposableObj = conn as IDisposable;
if (disposableObj != null)
{
disposableObj.Dispose();
}
}
当然更直接的代码是:
try
{
//do something
}
finally
{
conn.Close();
}
在C#中,using的用法,并没有走到多远,其实就是try ... finally方式和IDiposible接口的配合使用。
其实foreach的关键字也起到简化编写异常安全代码的作用:
foreach (Object obj in list)
{
//do something
}
其实相当于:
IEnumerator iter = list.GetEnumerator();
try
{
while (iter.MoveNext())
{
Object obj = iter.Current;
//do something
}
}
finally
{
IDisposable disposableObj = iter as IDisposable;
if (disposableObj != null)
{
disposableObj.Dispose();
}
}
在foreach中,也使用了try ... finally,用于Dispose实现了IDisposable的IEnumerator对象。
Anders Hejlsberg曾说,在优秀的代码中,会有大量的try...finally形式的代码存在。为了减少try...finally的使用,C#引入了lock、using、foreach。
但是异常处理在C#中,缺少了异常规范(exception specification),这是令人遗憾,Anders Hejlsberg曾经发表过一些论述,很有道理,但是没有异常规范的C#是令人遗憾的。