前景提要: 編寫程序時,也許你不經意間,就不知不覺的使程序代碼,發生了裝箱和拆箱,從而降低了效率,不要說就發生那麼一次兩次,如果說是程序中發生了循環、網絡程序(不斷請求處理的)等這些時候,減少裝箱和拆箱,是優化程序提高效率的一種途徑。不僅跬步,無以至千裡,不積小流,無以至江河。優化從點點滴滴做起。 一、裝箱拆箱概念: 這裡是官方定義:http://msdn.microsoft.com/zh-cn/library/yz2be5wk.aspx 裝箱:值類型→引用類型 拆箱:引用類型→值類型 二、為什麼說裝箱,拆箱消耗資源(內存、cpu)? 2.1 圖說裝箱、拆箱 說明:裝箱。值類型存放於內存棧上,引用類型存放於內存對上。如果將已定義好的值類型(棧上的數據)修改至引用類型(堆上), 2.2 圖文說 裝箱過程 值類型存儲(沒有堆什麼事): 引用類型存儲(棧中存儲的是,堆中對象的地址,堆中是實際對象) 這時如果,將值類型變成引用類型,存儲的位置發生變化,發生了裝箱,而且為了拆箱,現在引用類型的存儲模式也不僅僅是以上引用類型的存儲模型了,值類型的類型也會進行相應的存儲,以方便在拆箱時候,轉換成相應裝箱時的類型。 這樣可以看出,裝箱,其實比你直接定義成一個引用類型,給家消耗了內存,以及增加了計算量(消耗了cpu)。這是原理級別的解釋,跟深入的,我也不太清除。只能分析到CLR這一步。 三、淺談ToString() 估計大家都知道,C#所有的類型基類(父類)均是Object,而Object中,提供的能叫子類繼承的方法就那麼幾個,virtual 的ToString就是其中之一,所以說,c#中所有的類型均有這個ToString方法。下面就淺談一下ToString方法在裝箱拆箱中的一二。 3.1 針對普通值類型 以Int32為例(Struct) int a=123; string b=a.ToString(); 請問這是發生裝箱了嗎? 答:值類型→引用類型,oh,裝箱!! 解答:只單純的看裝箱定義,這確實符合裝箱的定義。但是,別忘記了ToString是基類的虛方法,子類是否對其有重寫。 int 的 戶口祖籍 int(C#語言)→Int32(CLR,oh是個結構,struct) →extends System.ValueType(查看IL代碼,發現了)→extends System.Object(這是終極祖宗啊!這裡有ToString啊) 這是Int32中對ToString方法的重寫: public override string ToString() { return Number.FormatInt32(this, null, NumberFormatInfo.CurrentInfo);} 接下來就是內部的實現了,我去,看不到了啊?怎麼辦? 對了編寫代碼,查看IL代碼。 可以看出這裡沒有發生裝箱啊!具體的深入內部實現可以借助反編譯工具,查看,如ILSpy、reflecter、ILdasm等。 3.2枚舉類型 那麼所有的值類型是不是使用Tostring方法,均不涉及裝箱操作呢?這個也不盡然,可是嘗試一下枚舉類型。 枚舉類型,是一個值類型。 示例: enum TestEnum { Test1, Test2 } string test = TestEnum.Test1.ToString(); //這句話是否發生裝箱操作 3.2.1 內部原理 首先查看枚舉中的ToString方法,這裡重寫了ToString方法 public override string ToString() { return InternalFormat((RuntimeType) base.GetType(), this.GetValue());} 查看InternalFormat方法的實現 private static string InternalFormat(RuntimeType eT, object value) { if (eT.IsDefined(typeof(FlagsAttribute), false)) { return InternalFlagsFormat(eT, value); } string name = GetName(eT, value); if (name == null) { return value.ToString(); } return name; } 通過查看可知eT.IsDefined(typeof(FlagsAttribute), false)、GetName(),這裡使用了反射,可能會有性能的損失,但是不會有裝箱操作 但GetName(eT,value),中的value參數是InternalFormat中的參數,這裡的參數是object類型, 而InternalFormat((RuntimeType) base.GetType(), this.GetValue())調用時,這裡的使用了this.GetValue這個方法來傳遞這個object參數 接下來查看 GetValue方法的實現啦 可以看出關於這個GetValue方法中發生了,裝箱操作,return (bool) *(((sbyte*) ptrRef)); 這個一個值類型,而GetValue需要的返回值是:Object類型 結論,枚舉中重寫的ToString方法不僅使用到了裝箱操作,而且還是用到了大量的反射。 綜上所述,使用枚舉時,只是針對值類型操作,增加幾個常量狀態switch-case,以及不涉及取出枚舉定義的值(ToString)則是非常方便的,快速的。 但是要是經常使用枚舉的ToString取得枚舉的定義值,則不建議使用。這裡是非常不合時宜的。可以直接使用靜態類代替即可(使用空間換取時間) 3.3 分析網絡大牛的技術博客 原本裝箱、拆箱感覺寫的差不多了,但是看到網上那麼多大牛、那麼寫感覺有點不合適啊!(不要被他們所謂的比較性能嚇到哦) 3.3.1 博客地址:http://www.cnblogs.com/XmNotes/archive/2010/09/18/1830355.html 這是第一個:性能相差7千倍的ToString方法 的博客 解說:看到標題,第一句想說的是,我靠!這麼雷人啊。7千倍啊! 但是一看代碼你就知道他在干嘛了 var day = DayOfWeek.Wednesday; //這可是枚舉啊 for (int i = 0; i < 1000000; i++) { value = day.ToString(); } 百萬級別的反射、裝箱。你坑人呢吧,不說實際有沒有這麼百萬級別的數量和這麼頻繁的操作,就說有你這麼用的嗎! 一種是你直接返回一個值類型的星期,最後表現層給你轉換一下,即使這裡裝箱、拆箱也就是這麼一次兩個,還能百萬級別的刷啊! 還有就是類似你的第二種,直接就是操作引用類型的,如果像你這樣百萬級別的在轉換一下,弄成靜態常量。 結論是:舉例要以事實做依據,不要做不符合實際的事情。不同的方法、類庫用於適合的場景。這裡不僅僅反射會耗時,裝箱操作也會造成資源的消耗 3.3.2 博客地址:http://www.cnblogs.com/yjmyzz/archive/2010/09/19/1830766.html 這是第二個:也談枚舉ToString()性能的改進 的博客 解說:我不理解樓主在干嗎,你定義的靜態類,在第一次使用的時候,就已經將枚舉裝到靜態變量dictionary中了,常駐內存了,直到程序結束才推出。 類似於你沒事循環讀取百萬級別的一個靜態變量啊! 而使用枚舉的ToString方法,是你在百萬級別的反射、裝箱數據啊! 我暈啊!枚舉是這樣用,這樣理解的嗎? 如果這幾個定義你常用、百萬級別讀取的話,你能不能稍微浪費點內存啊!直接這樣用啊(空間換取時間) public static class EnumLoginError { public static string 用戶名不存在 { get{return "用戶名不存在";}} public static string 密碼錯誤 { get{return "密碼錯誤";}} public static string 用戶被鎖定 { get{return "用戶被鎖定";}} public static string 未知錯誤 { get{return "未知錯誤";}} } 結論是:不要做畫蛇添足的事情,對待事物要有懷疑精神。還是合適的工具做合適的活,合適的人做合適事情。 四、能夠減少拆箱裝箱,常用的替代類庫 4.1 推薦使用泛型集合 命名空間System.Collections.Generic List<T>類似於ArrayList,ArrayList的升級版。 各種方法:Sort()、Max()、Min()、Sum()… Dictionary<K,V>類似於Hashtable,Hashtable的升級版。 T,K,V就像一把鎖,鎖住集合只能存某種特定的類型,這裡的T,K,V也可以是其它字母