陈铭 Microsoft .NET MVP
“开心辞典、急智问答”——指出下面的程序段中包含的所有box和unbox操作: public interface IMovable { void Move(int newx, int newy); } public struct Point : IMovable { public int X, Y; public void Move(int newx, int newy) { X = newx; Y = newy; } } ... Point pt1; pt1.X = 1; pt1.Y = 1; ArrayList al = new ArrayList(); //1 al.Add(pt1); Point pt2 = (Point)al[0]; Type t = pt2.GetType(); Point temp = (Point)al[0]; //2 temp.Move(5, 5); ((IMovable)al[0]).Move(4, 4); DateTime dt = DateTime.Now; //3 string str2 = dt.ToString(); string str1 = pt1.ToString(); int m = 1; //4 string str3 = m + ", " + dt; ... 在条款X中我们曾经介绍过box操作包含了内存分配、对象拷贝等“繁重”的工作,因此正确识别出 程序中存在的隐式box/unbox操作对提高.NET程序的性能至关重要。 单就概念而言,box和unbox操作非常易于理解:当程序期待一个引用类型的对象,而实际得到的 是一个值类型对象的时候,就必须通过box操作将其转换成引用类型;反之,如果程序期待一个简单 的值类型对象,而得到的却是经过box包装的,那么就必须再通过unbox操作将它转换回来。 然而,在实际使用当中——尤其是与接口继承、多态等面向对象特性混合使用的时候,正确判断 box和unbox操作的存在就不那么容易了。因此,还是让我们逐段的分析上面提到的程序,看看你究 竟掉进了几个陷阱。J ArrayList al = new ArrayList(); al.Add(pt1); Point pt2 = (Point)al[0]; Type t = pt2.GetType(); 这是box和unbox操作最简单基本的形式:由于ArrayList的Add方法需要一个Object类型的参数, 而程序实际提供的参数pt1是一个值类型对象,所以编译器必须产生相应的box指令,在调用Add方 法之前生成一个与pt1对应的引用类型对象,并以该对象做为Add方法的参数。 由于.NET在数组实现上使用了一些特殊的手法,值类型数组的各种操作并不需要box和unbox (参见条款2)。 在接下来的一条语句中,al[0]返回一个Object类型的对象引用,程序需要将其强制转换成Point 类型,所以编译器也必须产生相应的unbox指令来完成这种转换。 最后,GetType是Object的成员函数,而ValueType和Point都没有改写该函数(由于GetType不 是虚函数,改写GetType需要使用new关键字),所以这里实际调用的是从Object继承而来的GetType 成员函数。显然,这个函数总是假设自己作用于一个引用类型的对象,所以,编译器必须在调用 GetType函数之前对pt2进行box操作。 相比之下,接下来的代码要稍显复杂一些了,我们需要调用Point类的Move方法,那么我们有两个选择: 一是先将al[0]转换成Point对象,然后调用其方法: Point temp = (Point)al[0]; temp.Move(5, 5); 我们前面已经分析过,这里强制类型转换的过程中实际包含了unbox操作。由于Move是Point的成员函数, 所以可以直接调用temp对象的Move方法而不需要再进行box操作。 需要注意的是,由于C#编译器的实现,temp实际上是unbox操作之后在堆栈上建立的新的Point对象。 所以调用temp对象的Move函数并不会影响ArrayList中al[0]的值。即使将这两个语句合并起来也无济于事: ((Point)al[0]).Move(5, 5); //同样不能改变al[0] 如果需要实际修改al[0],就必须将temp写回到ArrayList中去: Point temp = (Point)al[0]; temp.Move(5, 5); al[0] = temp; 另外一种方法是将其转换成IMovable接口,再调用Move方法: ((IMovable)al[0]).Move(4, 4); 也许有些出乎你的意料,使用这种方法并不需要任何的box/unbox操作。al[0]返回一个Object对象的引用, 将这样一个引用转换成一个IMovable接口显然不需要box操作,而调用一个接口函数同样无需box或者unbox。 使用这种方法的另一个好处是避免了前面提到的拷贝工作。通过接口函数调用,我们“就地”修改了 ArrayList当中的Point对象的值,既减少了代码量,同时也提高了程序的性能。
|
本文地址:http://com.8s8s.com/it/it45952.htm