"不要多次释放对象"的小随笔

【题外话】

之前大部分时间都在用Visual Studio 2008做开发,虽然也点开过代码分析,但是一看一大串内容,尤其是一大串针对命名的建议,就果断关闭了。这次实习使用的Visual Studio 2012,发现代码分析默认去掉了很多内容,显示的也都是比较重要并需要改进的地方,所以也都认真研究了一下。

 

【文章索引】

  1. 问题和解决方法
  2. 为什么这样去做
  3. 相关链接

 

【一、问题和解决方法】

应该有人会写如下的代码吧,为了释放资源,我们把打开的东西都关闭掉,貌似没有什么问题。

 1 FileStream fs = null;
 2 StreamReader sr = null;
 3 
 4 try
 5 {
 6     fs = new FileStream(@"F:\test.txt", FileMode.Open, FileAccess.Read);
 7     sr = new StreamReader(fs);
 8 
 9     String content = sr.ReadToEnd();
10 }
11 finally
12 {
13     if (sr != null)
14     {
15         sr.Close();
16     }
17 
18     if (fs != null)
19     {
20         fs.Close();
21     }
22 }

当然,喜欢用using的同学也可能会写如下的代码:

1 using (FileStream fs = new FileStream(@"F:\test.txt", FileMode.Open, FileAccess.Read))
2 {
3     using (StreamReader sr = new StreamReader(fs))
4     {
5         String content = sr.ReadToEnd();
6     }
7 }

但是这两种代码如果使用代码分析会出现什么情况呢,如下图。

比较有意思的是,这里提示的是“不应对一个对象多次调用 Dispose”,为什么会是“一个对象”呢?

通过翻阅MSDN中的CA2202(链接在文后),我们可以查到原因是这样的,“某个方法实现所包含的代码路径可能导致对同一对象多次调用 IDisposable.Dispose 或与 Dispose 等效的方法(例如,用于某些类型的 Close() 方法)”,MSDN中直接给出了解决方法就是不要关闭StreamReader,而是直接关闭FileStream。

 

【二、为什么这样去做】

MSDN给出的方法为什么要这样做呢?出于好奇心,首先拿上述的代码单步调试一下:

在执行完StreamReader的Close之后,StreamReader的BaseStream指向了null,同时fs也变为了不能读取,但fs不是null。

然后我们用Reflector找到StreamReader的实现(在mscorlib.dll文件中)如下:

 1 public override void Close()
 2 {
 3     this.Dispose(true);
 4 }
 5 
 6 protected override void Dispose(bool disposing)
 7 {
 8     try
 9     {
10         if ((this.Closable && disposing) && (this.stream != null))
11         {
12             this.stream.Close();
13         }
14     }
15     finally
16     {
17         if (this.Closable && (this.stream != null))
18         {
19             this.stream = null;
20             this.encoding = null;
21             this.decoder = null;
22             this.byteBuffer = null;
23             this.charBuffer = null;
24             this.charPos = 0;
25             this.charLen = 0;
26             base.Dispose(disposing);
27         }
28     }
29 }

StreamReader在执行Close时竟然执行了this.stream(BaseStream)的Close,然后将BaseStream再指向null,这就解决了之前为什么提示不要多次释放 一个 对象,其实是StreamReader的Close已经释放了一次而已。当然,不仅仅是StreamReader是这样子,StreamWriter、BinaryReader等等也都是这样子的。

可是,为什么MSDN的例子给的是关闭流而不是关闭读取器呢?

翻阅了网上也没有找到权威的资料,所以个人总结了几点如下仅供参考:

1、关闭StreamReader等其实已经关闭了其BaseStream,但容易使开发者误以为BaseStream没有关闭而继续使用导致抛出异常,所以关闭最基础的流会更好些。

2、StreamReader等本身并没有使用非托管的内容,所以也无需主动执行Close,让GC去做就好了。

 

【三、相关链接】

1、CA2202:不要多次释放对象:http://msdn.microsoft.com/zh-cn/library/ms182334(v=vs.110).aspx

posted @ 2013-04-10 23:29 大魔王mAysWINd 阅读(...) 评论(...) 编辑 收藏