今天看了《何时readonly 字段不是 readonly 的?结果出呼你想象!!!》,实在是让我看花了眼。关于此文中S3的方式其实也已经不必多说。关键却是S这一个结构。(我最讨厌这样的命名!!)
为了便于说明,我重新写了一个类似的代码:
class Program { static void Main(string[] args) { Fuck f = new Fuck(100); f.Change(1000, out f); Console.Read(); } } struct Fuck { public readonly int Num; public Fuck(int value) { Num = value; } public void Change(int value, out Fuck data) { System.Console.WriteLine(this.Num); data = new Fuck(value); System.Console.WriteLine(this.Num); } }
运行结果自然是:
100
1000
这是为何呢?我把Fuck的定义改成了class 即
Fuck { public readonly int Num; public Fuck(int value) { Num = value; } public void Change(int value, out Fuck data) { System.Console.WriteLine(this.Num); data = new Fuck(value); System.Console.WriteLine(this.Num); } }
运行结果是:
100
100
问题肯定在于值类型与引用类型处理机制上。尽管在C#中,所有的类型都以object的形式存在,但在内部,值类型与引用类型的处理机制仍然不同。
让我们回到 readonly 的问题上来。
readonly 关键字与 关键字不同。const 字段只能在该字段的声明中初始化。readonly 字段可以在声明或构造函数中初始化。因此,根据所使用的构造函数,readonly 字段可能具有不同的值。另外,const 字段为编译时常数,而 readonly 字段可用于运行时常数
摘自于MSDN
根据这段话 ,readonly 字段是可以在运行时更改的。因此,readonly 在除构造函数外的函数中肯定也可以发生改变。
我们不仿来看看 readonly 字段 是怎么改变的
进入 Change 函数之后我们可以发现,变量 f 的地址并未发生改变.同时,this关键字指身的地址,与传入的参数 data 相同。
执行完 data = new Fuck(value) 之后,this关键字指向的地址,data指向的地址仍然没有改变,但是该地址的值发生了变化。
这么看来,问题就出在于 data = new Fuck(value) 这句话上。可能有些朋友对于 out 关键字很疑惑,其实跟 out 关键字并没有太大的关系。
这里又让我们回到了C#中对于值类型的处理上。
基于值类型的变量直接包含值。将一个值类型变量赋给另一个值类型变量时,将复制包含的值。
摘自MSDN
也就是说,在给 data 赋值的时候,C#将 new Fuck(value) 的值按地址复制到了data 所指向的地址中。这也是为什么把Fuck的声明改成了class之后,这个现象就并不存在了。
与引用类型变量的赋值不同,引用类型变量的赋值只复制对对象的引用,而不复制对象本身
摘自MSDN
这么看来。要改变 readonly 的值并不需要这么复杂的过程。
轻轻修改一下代码:
unsafe static void Main(string[] args) { Fuck f = new Fuck(100); Fuck* pf = &f; int* p = (int*)pf; *p = 123456; f.Change(1000, out f); Console.Read(); }
运行结果是:
123456
1000
不过的确,像那种奇怪的编码方式是会引起一些奇怪的现象。不知道这算不算.net的BUG