CLR via C# 第五章:使用接口修改已装箱类型中的字段

有的语言(比如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上了。不过并没有进行最后的猜测验证。

一个疑问:本书上说:在值类型中定义的成员不应该修改类型的任何实例字段,也就是说,值类型应该是不可变的。此处不明白。希望大家给予帮助!!!万谢。。。。。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值