有的语言(比如CLI/C++)允许更改已装箱值类型中的字段,但是C#是不允许的。不过我们可以通过使用接口来欺骗C#.
- 如果是比如Int,double之类的值类型,则可以直接修改已装箱值类型的方法来修改值。
int ww = 2;
object cc = ww;
cc = 19;
Console.WriteLine(cc);
输出结果是19
- 但是如果是自定义的值类型,就不能这样修改了。
struct Point
{
public int m_x;
public int m_y;
public Point(int x,int y)
{
m_x = x;
m_y = y;
}
public void Change(int x,int y)
{
m_x = x;
m_y = y;
}
public override string ToString()
{
return string.Format("m_x:{0},m_y:{1}",m_x,m_y);
}
}
以上是自定义的一个结构体值类型。
static void Main(string[] args)
{
Point p = new Point(1,1);
Console.WriteLine(p);
p.Change(2,2);
Console.WriteLine(p);
object o = p;
Console.WriteLine(o);
((Point)o).Change(3,3);
Console.WriteLine(o);
Console.ReadKey();
}
此处输出结果是
m_x:1,m_y:1
m_x:2,m_y:2
m_x:2,m_y:2
m_x:2,m_y:2
输出结果分析:
第一个WriteLine处,p装箱,输出堆栈上的已装箱值类型。
之后未装箱值类型p调用值类型struct上定义的方法change,改变栈上的p的值(p的值为2,2)。此处未装箱
第二个WriteLine处,p装箱(上一步中p的值已经被修改为2,2),输出堆栈上的已装箱值类型。
object o = p;
Console.WriteLine(o);
先将p装箱,并将引用放进o中。然后输出结果2,2。此处没有疑问
((Point)o).Change(3,3);
Console.WriteLine(o);
因为o是已装箱值类型的引用,这个已装箱引用类型上没有change方法(只有字段以及引用类型两个额外的成员,同步快索引和类型指针)。所以将o拆箱,放在栈上一个临时数据中。这个临时数据的值改成3,3。等change方法执行完后,这个临时数据就被删掉了。而堆栈上的o的值并没有改变。
方法改进:使用接口欺骗C#
interface IChangeBoxedPoint
{
void Change(int x,int y);
}
struct Point:IChangeBoxedPoint
{
public int m_x;
public int m_y;
public Point(int x, int y)
{
m_x = x;
m_y = y;
}
public void Change(int x, int y)
{
m_x = x;
m_y = y;
}
public override string ToString()
{
return string.Format("m_x:{0},m_y:{1}", m_x, m_y);
}
}
创建接口,并在接口中实现change方法。
static void Main(string[] args)
{
Point p = new Point(1, 1);
Console.WriteLine(p);
p.Change(2, 2);
Console.WriteLine(p);
object o = p;
Console.WriteLine(o);
((Point)o).Change(3, 3);
Console.WriteLine(o);
Console.WriteLine("************");
((IChangeBoxedPoint)p).Change(4,4);
Console.WriteLine(p);
((IChangeBoxedPoint)o).Change(5,5);
Console.WriteLine(o);
Console.ReadKey();
}
前面几个输出和前面的是一致的。关键的是最后两个输出:
分析:
未装箱的p转换成IChangeBoxedPoint类型,此处要进行装箱。(未装箱的值类型转换成接口类型时都要进行装箱,因为接口变量必须包含对堆上的一个对象的引用)。此时在堆上创建了一个IChangeBoxedPoint对象,执行Change方法,将这个对象的值变成4,4.但是执行结束后。这个对象没有任何引用了(这可能调用垃圾回收机制,销毁这个对象),,之后输出P,这时又进行又进行装箱(应该是会装箱,未检测)。输出2,2。
最后一个输出:
因为o本身就已经进行过装箱。所以这里没有进行装箱。
执行change方法时,改变为5,5
然后输出5,5
总结:其实最后两个输出结果的不同主要是因为没有保存装箱后的对象
此处有个一猜测: ((IChangeBoxedPoint)o).Change(5,5);
这个地方虽然没有进行装箱:但是应该会创建一个IChangeBoxedPoint对象实例。然后将这个对象的引用保存在o上了。不过并没有进行最后的猜测验证。
一个疑问:本书上说:在值类型中定义的成员不应该修改类型的任何实例字段,也就是说,值类型应该是不可变的。此处不明白。希望大家给予帮助!!!万谢。。。。。