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在这背后的 行为,达人们轻点拍。欢迎大家多交流:-)