【题外话】
之前大部分时间都在用Visual Studio 2008做开发,虽然也点开过代码分析,但是一看一大串内容,尤其是一大串针对命名的建议,就果断关闭了。这次实习使用的Visual Studio 2012,发现代码分析默认去掉了很多内容,显示的也都是比较重要并需要改进的地方,所以也都认真研究了一下。
【文章索引】
应该有人会写如下的代码吧,为了释放资源,我们把打开的东西都关闭掉,貌似没有什么问题。
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