記錄這篇文章的靈感來源來自今天下班前與同事的小小爭論,我現在開發的一個項目中,有這樣一段代碼:
public string ToXML() { string strXml = string.Empty; try { MemoryStream ms = new MemoryStream(); XmlSerializer xml = new XmlSerializer(this.GetType()); xml.Serialize(ms, this); byte[] arr = ms.ToArray(); strXml = Encoding.UTF8.GetString(arr, 0, arr.Length); return strXml; } catch { return ""; } }
同事說象MemoryStream這類資源,應該用using包起來自動釋放資源,否則會有記憶體流失問題。在using的使用上,我也同意應該使用using,但由於這類風格的代碼在原項目中非常多(有一部分曆史原因),如果一一修改,工作量太大,時間不允許。於是我就在內心評估:如果不改,現在這種代碼的風險到底有多大?
我想很多人都知道using(Resource res = new Resrouce){},其實相當於
Resource res = new Resrouce
try{}
catch{}
finally{res.Dispose();}
對比與現有代碼的區別,無非就是資源沒有調用Dispose()釋放,但是CLR有強大的GC(記憶體回收)機制,方法調用完成後,方法體中建立的託管資源如果不再被使用,也一併會被GC列為可回收對象,所以就算開發人員沒有手動調用Dispose,其實CLR也會幫我們做這件事情,只是時機可能會晚一些而已。
於是有了下面的測試:
1.先建立一個樣本用的Class
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.IO;using System.Xml.Serialization;namespace Model{ public class SampleClass { public string Name { set; get; } public string ToXMLNoUsing() { string strXml = string.Empty; try { MemoryStream ms = new MemoryStream(); XmlSerializer xml = new XmlSerializer(this.GetType()); xml.Serialize(ms, this); byte[] arr = ms.ToArray(); strXml = Encoding.UTF8.GetString(arr, 0, arr.Length); return strXml; } catch { return ""; } } public string ToXMLWithUsing() { string strXml = string.Empty; try { using (MemoryStream ms = new MemoryStream()) { XmlSerializer xml = new XmlSerializer(this.GetType()); xml.Serialize(ms, this); byte[] arr = ms.ToArray(); strXml = Encoding.UTF8.GetString(arr, 0, arr.Length); } return strXml; } catch { return ""; } } }}
這其中的ToXML為了測試方便,故意分成了二個版本(一個不用using,一個用using)
2.再建立一個Console程式(命名為WithUsing),寫一段測試代碼:
using System;using System.Diagnostics;using Model;namespace WithUsing{ class Program { static void Main(string[] args) { Console.WriteLine("開始折騰-WithUsing..."); Stopwatch watch = new Stopwatch(); int max = 100000; watch.Reset(); watch.Start(); for (int i = 0; i < max; i++) { SampleClass c = new SampleClass() { Name = i.ToString().PadLeft(1024, '0') }; c.ToXMLWithUsing(); } watch.Stop(); Console.WriteLine("完成,{0}次操作共耗時:{1}毫秒,平均{2}毫秒/次!", max, watch.ElapsedMilliseconds, watch.ElapsedMilliseconds /(decimal)max); Console.ReadKey(); } }}
3.再建立一個Console程式(命名為NoUsing),寫一段測試代碼:
using System;using System.Diagnostics;using Model;namespace NoUsing{ class Program { static void Main(string[] args) { Console.WriteLine("開始折騰-NoUsing..."); Stopwatch watch = new Stopwatch(); int max = 100000; watch.Reset(); watch.Start(); for (int i = 0; i < max; i++) { SampleClass c = new SampleClass() { Name = i.ToString().PadLeft(1024, '0') }; c.ToXMLNoUsing(); } watch.Stop(); Console.WriteLine("完成,{0}次操作共耗時:{1}毫秒,平均{2}毫秒/次!", max, watch.ElapsedMilliseconds, watch.ElapsedMilliseconds / (decimal)max); Console.ReadKey(); } }}
編譯後,同時運行這二個程式,同時利用工作管理員觀察記憶體使用量情況:
反覆多次運行比較,發現其實二者佔用的記憶體幾乎完全相同,這說明GC還是很給力的!
而且從執行時間上看,不用Using,反而更快,這也容易理解:用Using相當於每次都要調用Dispose()方法,這會帶來一些系統開銷;而不用Using,GC會在適當的時機批量回收資源,效能反而更好。(當然:這個結論不是要誤導大家不用using,對於using還是推薦使用的!我的用意在於大家對於一些具體問題要具體分析,不可純教條主義,一味迷信某些主流的觀點)