Freesc Huang @ HUST All Rights Reserved
2008-2-11
Keywords
.NET Framework,C#,實值型別,裝箱,拆箱,CLR
本文
半年之前,我曾經寫過一篇關於實值型別裝箱問題的短文(這裡),現在看來,有些東西當時還是沒有完全說開,這次特地拿了一個例子再來談談。理解這些問題,對於一個.NET程式員來說很基礎,也很重要,對我們理解CLR和編寫高效的程式都是很有協助的。至於什麼是實值型別,什麼是裝箱拆箱(box&unbox),在此不做贅述,先來看看下面短短的幾行代碼:
Code
1 internal struct Ticket
2 {
3 private String _start, _terminal;//起點和終點
4 private Int32 _distance;//距離
5
6 public Ticket(string start, string terminal, Int32 distance)
7 {
8 _start = start;
9 _terminal = terminal;
10 _distance = distance;
11 }
12 /**//// <summary>
13 /// 重新訂票
14 /// </summary>
15 /// <param name="newTerminal">新的終點站</param>
16 /// <param name="newDistance">到終點站的距離</param>
17 public void Rebook(String newTerminal, Int32 newDistance)
18 {
19 _terminal = newTerminal;
20 _distance = newDistance;
21 }
22 /**//// <summary>
23 /// 重寫System.ValueType的ToString方法
24 /// </summary>
25 public override String ToString()
26 {
27 return String.Format("From {0} To {1} , {2} km",
28 _start,
29 _terminal,
30 _distance);//在方法的內部,_distance被裝箱
31 }
32 }
33
34 public sealed class Program
35 {
36 public static void Main()
37 {
38 Ticket t = new Ticket("北京", "漢口", 1225);
39 //實值型別執行個體t在這裡第一次被裝箱:Ticket-->Object-->override ToString
40 Console.WriteLine(t);
41
42 //顯示的裝箱
43 // Console.WriteLine(((Object)t).ToString());
44
45 t.Rebook("上海", 1400);
46 Console.WriteLine(t);
47
48 Object o = t;
49 Console.WriteLine(o);
50
51 ((Ticket)o).Rebook("廣州",2000);
52 Console.WriteLine(o);
53 }
54 }
程式很短很簡單,定義了一張火車票(Ticket)的結構,它只包括起點,終點和裡程,它是實值型別(結構派生自System.ValueType)。而我們主要關注的是圍繞這張火車票的幾個輸出。
首先,建立了一個火車票的執行個體t(第38行), 初始化為北京到漢口,1225公裡,接著第一次調用Console.WriteLine,因為Console.WriteLine() 沒有參數為Ticket的重載,這裡會對t進行裝箱(就像這裡提到的一樣),而這個“已裝箱”的票(姑且稱作tII)會被CLR預設為是個Object,然後在這個Object執行個體tII上調用ToString(),而此時CLR在這個已裝箱的Ticket的方法表中發現這個類型重寫了ToString()方法,它會隱式的調用這個方法讓我們順利的得到顯示"From 北京 To 漢口,1225 km"。也許我注釋掉的那句代碼會讓您更好的理解這個過程。
隨後,還是在那個實值型別的t(而非tII)上調用Rebook,改成去上海,1400公裡。然後再調用WriteLine,經過與前面相同的過程,我們如願得到了輸出"From 北京 To 上海,1400 km"。
接著,我們顯式地將t裝箱,其實這兩行代碼(第48,49行)跟第43行注釋掉的代碼是一樣的,WriteLine()方法本身就有一個參數為Object的重載。於是我們仍能如願得到"From 北京 To 上海,1400 km"。
接下來這一句比較有趣了,我們將o指向的tII拆箱,將相應的欄位複製到堆棧上一個實值型別執行個體tIII中,然後再對tIII調用Rebook方法,將其改為從北京到廣州,注意,這裡的修改是在堆棧上直接進行的。與託管堆上的o沒有任何關係,於是此時調用ConsoleWriteLine(o);輸出仍然為"From 北京 To 上海,1400 km"。
為了不讓tIII成為游離於我們控制之外的垃圾,我們讓Rebook立刻返回給我們這個tIII,並複製給一個叫t2的執行個體,並輸出,這樣我們才得到了修改過的最終結果"From 北京 To 廣州,2000 km"。
程式輸出如下:
程式碼在這裡
本文算作是對之前那篇隨筆的補充和擴充,也希望通過這個例子能讓大家更加明白實值型別裝箱拆箱的原理和CLR在這背後的 行為,達人們輕點拍。歡迎大家多交流:-)