[C# Tips]再谈值类型的装箱和拆箱

本文通过一个火车票结构体的例子,深入探讨了.NET Framework中值类型装箱和拆箱的过程,以及CLR如何处理这些操作,帮助程序员理解并编写更高效的代码。

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


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值