陈铭 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对象的值,既减少了代码量,同时也提高了程序的性能。
接下来是关于值类型对象ToString方法的调用:
DateTime dt = DateTime.Now;
string str2 = dt.ToString();
string str1 = pt1.ToString();
我们前面已经分析过调用GetType方法的情况,这里的ToString和GetType一样是Object的成员函数,
那么调用ToString不也是显然需要box操作的么?先别急,还是让我们逐个仔细分析:
|
本文地址:http://com.8s8s.com/it/it45952.htm