我们实例化了两个不同的MathHelper,给PI赋予了不同的值,PI的值属于不同的实例,这也就验证了我们的结论.
4.readonly的内联写法
那有的童鞋说了,我还用过这样的写法,这说明了readonly可以在构建方法外赋值.如下所示:
1 public class MathHelper 2 { 3 public readonly float PI=3.15F; 4 }
其实,这是一种内联写法,是C#的一种语法糖,只是一种语法上的简化,实际它们也是在构造方法中进行初始化的.C#允许使用这种简化的内联初始化语法来初始化类的常量、read/write字段和readonly字段。
5.readonly赋值的第二种情况:如果我用static修饰readonly会发生什么?
前面讲const时,我们说过const是静态的,这种静态不可以显式指定,因此在const前加static会导致编译器编译失败.那我们把static修饰readonly会发生什么样的结果?
首先,我们确定,静态的是属于类的,此时的readonly我们不能通过构造函数来指定.
1 public class MathHelper 2 { 3 public static readonly float PI=3.14F; 4 }
调用:
1 static void Main(string[] args) 2 { 3 Console.WriteLine(MathHelper.PI); 4 Console.Read(); 5 }
结果与我们预期的一致:
但我们的疑问不会就此打住:既然static readonly也是属于类的,而且它的值也不能通过构造函数来赋值,那么编译器会像const一样把它的值写入IL代码中么?我们反编译其IL代码如下:
可以看到,这里并没有将值嵌入到代码当中.
因此,我们可以大胆地预测,这种写法不会造成支持程序集的跨版本问题.这里就不写验证的过程,留给各位读者朋友自行探索.
既然没有嵌入代码中,那么在程序运行的时候,它的值是在什么时候分配内存的呢?
我们引用《CLR via C#(第4版)》中的一句话来说明这个问题:对于静态字段,其动态内存是在类中分配的,而类是在类型加载到AppDomain时创建的,那么,什么时候将类型加载到AppDomain中呢?答案是:通常是在引用了该类型的任何方法首次进行JIT编译的时候.而对于前面第3点中的实例字段来说,其动态内存是在构造类型的实例时分配的.
三.什么是不可变的?
前面我们花了大量的篇幅说明const与readonly的变量什么时候才开始不可变,有的从一开始就不可变,有的是第一次加载的时候不可变,有的是出了构造函数后不可变,但是我们有一个十分关键的问题没有弄清楚:什么东西不可变?也许童鞋们很疑惑,值不可变呗!这话不完全对.
要想理解这个问题,我们需要明白const与readonly修饰的对象,也就是我们不变的内容.
const可以修饰基元类型:Boolean、Char、Byte、SByte、Int16、UInt16、Int32、UInt32、Int64、UInt64、Single、Double、Decimal和String。也可以修改类class,,但要把值设置为null。不可以修饰struct,因为struct是值类型,不可以为null.
对于基元类型来说,值是存储在栈上的,因此我们可以认为不变的是值本身,这里string是一个特殊的引用类型,这里它也存在值类型的特征,因此也可以认为它不变的是值本身.
对于readonly而言,readonly可以修饰任何类型.对于基元类型而言,我们可以认为它与const无异,但是对于引用类型,我们需要谨慎对待,不可想当然,下面我们通过实验来得出结论:
1 public class Alphabet 2 { 3 public static readonly Char[] Letters = new Char[] {‘A‘,‘B‘,‘C‘,‘D‘,‘E‘,‘F‘ }; 4 }
调用: