知識點
- 實值型別。
- 實值型別是在棧中分配記憶體,在聲明時初始化才能使用,不能為null。
- 實值型別超出作用範圍系統自動釋放記憶體。
- 主要由兩類組成:結構,枚舉(enum),結構分為以下幾類:
- 整型(Sbyte、Byte、Char、Short、Ushort、Int、Uint、Long、Ulong)
- 浮點型(Float、Double)
- decimal
- bool
- 使用者定義的結構(struct)
- 參考型別。
- 參考型別在堆中分配記憶體,初始化時預設為null。
- 參考型別是通過記憶體回收機制進行回收。
- 包括類、介面、委託、數組以及內建參考型別object與string。
概念
由於C#中所有的資料類型都是由基類System.Object繼承而來的,所以實值型別和參考型別的值可以通過顯式(或隱式)操作相互轉換,而這轉換過程也就是裝箱(boxing)和拆箱(unboxing)過程。
- 裝箱 是實值型別到 object 類型或到此實值型別所實現的任何介面類型的隱式轉換。對實值型別裝箱會在堆中分配一個對象執行個體,並將該值複製到新的對象中。
- 拆箱(unboxing)是從 object 類型到實值型別或從介面類型到實現該介面的實值型別的顯式轉換。unboxing操作包括:
檢查對象執行個體,確保它是給定實值型別的一個裝箱值。(拆箱後沒有轉成原類型,編譯時間不會出錯,但運行會出錯,所以一定要確保這一點。用GetType().ToString()判斷時一定要使用類型全稱,如:System.String 而不要用String。)
將該值從執行個體複製到實值型別變數中。
樣本
首先寫個簡單的控制台程式:
// Tutorial_boxing_unboxing.cs
// 裝箱與拆箱
using System;
class App
{
static void Main()
{
int i = 32;
object o = i; //隱式裝箱
Console.WriteLine("o = {0}", o);
Console.Read();
}
}
其中object o = i這裡我們進行了裝箱操作,然後我們用MSIL 反組譯工具查看下產生的.exe程式的內部機理。
1 .method private hidebysig static void Main() cil managed
2 {
3 .entrypoint
4 // 代碼大小 30 (0x1e)
5 .maxstack 2
6 .locals init ([0] int32 i,
7 [1] object o)
8 IL_0000: nop
9 IL_0001: ldc.i4.s 32
10 IL_0003: stloc.0
11 IL_0004: ldloc.0
12 IL_0005: box [mscorlib]System.Int32
13 IL_000a: stloc.1
14 IL_000b: ldstr "o = {0}"
15 IL_0010: ldloc.1
16 IL_0011: call void [mscorlib]System.Console::WriteLine(string,
17 object)
18 IL_0016: nop
19 IL_0017: call int32 [mscorlib]System.Console::Read()
20 IL_001c: pop
21 IL_001d: ret
22 } // end of method App::Main
其中第12行是我們的裝箱操作。(關於IL中出現的操作符代表的操作請查閱MSDN Library中的.NET開發/.NET Framework SDK/類庫參考/System.Reflection.Emit/OpCodes 類/OpCodes 欄位)
然後我們unboxing操作:
static void Main()
{
int i = 32;
Console.WriteLine("i = {0}", i);
Console.Read();
}
再用MSIL工具查看產生的.exe,如下結果:
.method private hidebysig static void Main() cil managed
{
.entrypoint
// 代碼大小 28 (0x1c)
.maxstack 2
.locals init ([0] int32 i)
IL_0000: nop
IL_0001: ldc.i4.s 32
IL_0003: stloc.0
IL_0004: ldstr "i = {0}"
IL_0009: ldloc.0
IL_000a: box [mscorlib]System.Int32
IL_000f: call void [mscorlib]System.Console::WriteLine(string,
object)
IL_0014: nop
IL_0015: call int32 [mscorlib]System.Console::Read()
IL_001a: pop
IL_001b: ret
} // end of method App::Main
在IL_000a行,我們發現這裡卻也出現了一個box!不過這步是在call System.Console::WriteLine(string, object)時發生的。我們對比前面我們手動boxing的IL代碼,發現在我們手動boxing後就沒有這步box了。為什麼呢?
當我們在調用一些方法的重載版本時,由於編譯器找不到符合給定參數類型的重載方法,此時編譯器便去尋找到的最接近的版本,然後使用找到的方法,而其參數卻是我們傳入的實值型別的基類如System.Object或者其實現的介面類型,接著編譯器為了求得與這個方法的原型一致,就必須對該實值型別進行裝箱操作(轉換成參考型別)。
照這個說法當我們不手動boxing時,在調用了Console.WriteLine()方法輸出一個Int32類型值時,系統就要自動進行boxing。也就是說如果我們要對該輸出操作作5000次的迴圈,系統就要做5000次的boxing。這樣對效能便會有一定的影響,而且要使迴圈次數是100,000,000次呢,或者跟多!
此時我們便要想如何消除這不應該的效能損失!正如第一個程式是展示的,我們可以在需要的地方先進行boxing,這個原理很簡單,我們可以聯想到類似的做法:
//當我們如下時:
for (int i = 0; i < arr.Length; i++)
{
//
}
//我們更因該這樣:
int L = arr.Length;
for (int i = 0; i < L; i++)
{
//
}
這樣,我們只要一次boxing,就可以避免讓系統重複的做這個操作。
用途
像在調用Console.WriteLine()的過程中系統自動進行boxing一樣,當我們在調用其它的一些方法的重載版本進行操所時,為了避免由於無謂的隱式裝箱所造成的效能損失,在執行這些多類型重載方法之前,最好先對值進行裝箱。一般是在處理大量資料需要對類型進行裝箱操作。
本篇內容參考自MSDN文檔。 |
本Blog中所有內容皆以“現狀”提供且沒有任何擔保,同時也沒有授予任何權利。 This posting is provided "AS IS" with no warranties, and confers no rights. |