【題外話】
之前大部分時間都在用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