近日,有同事写的http下载模块出现了一些问题,在Review代码的过程中发现一个奇怪的地方:
针对从WebResponse中取出来的Stream,在用完以后,对于Stream手动依次调用了Close、Dispose。
if (reader != null)
{
reader.Close();
reader.Dispose();
}
而紧接着,对于WebResponse的操作却仅仅调用了Close。
if (response != null)
{
response.Close();
}
而实际上,针对WebResponse的使用范例,无一例外都是使用了using关键字来处理Dispose的。
using (WebResponse response = request.GetResponse())
{
...
response.Close();
}
这表明,WebResponse在使用完后,需要进行Dispose,于是我尝试给他加上Dispose调用,却发现通过response的引用,无法点出Dispose来,询问原作者后,也表示因为点不出来,所以认为没这个接口,就没有调用了。但是,查看WebResponse的定义,发现它是有继承IDisposable的。
namespace System.Net
{
public abstract class WebResponse : MarshalByRefObject, IDisposable, ISerializable
{
...
}
}
那么,为什么会点不出Dispose方法来呢?
原来,WebResponse对IDisposable接口的实现采用了显式实现。
void IDisposable.Dispose()
{
...
}
对于显式实现的接口,无法在子类的引用中调用该实现,而必须使用基类的引用。
if (response != null)
{
response.Close();
(response as IDisposable).Dispose();
}
这样,可以成功调用Dispose,对其进行释放。
那么,为什么使用using关键字时可以成功释放呢?那是因为using在CSharp中的潜规则是,将赋予它的引用,强转为IDisposable,然后调用它的Dispose接口。
using (object)
{
...
}
上述代码相当于
...
(object as IDisposable).Dispose();
特别的,如果传入的object并没有继承自IDisposable,编译时会直接报错。
对于踩了这样一个坑,说明这位同事的基础知识掌握还不够,并且在技术选型时有些盲目自信,在没有完全掌握基础细节的情况下,擅自改变了范例代码的常见用法,导致了这个结果。
在此,呼吁各位基础还不扎实的同学,在学习范例代码的时候要么彻底搞明白这么写的原因,要么就严格模仿,不要擅自去修改,以免采坑,好自为之。