常量
常量
是从不变化的符号. 它的值必须能在编译时确定. 编译器将常量值保存到程序集的元数据中. 这意味着只能定义编译器识别的基元类型常量.
- Boolean
- Char
- Byte
- SByte
- Int16
- UInt16
- Int32
- UInt32
- Int64
- UInt64
- Single (float)
- Double
- Decimal
- String
特殊的是: C#允许定义非基元类型的常量变量,但是必须为null;
public const SomeType Empty = null;
定义常量将导致创建元数据,总是被视为静态成员,而不是实例成员.
C#不允许为常量指定static关键字,因为常量是总是隐式的static.
常量是怎么保存的?
- 代码引用常量符号时, 编译器在定义常量的程序集的元数据中查找符号,提取常量的值,将值嵌入生成的IL代码中.
- 由于常量的值直接嵌入代码,所以运行时不需要为常量分配任何内存.
- 不能获取常量的地址,也不能以传引用的方式传递常量
- 不能很好的支持跨程序集的版本控制
常量的跨程序集问题
// 程序集A 类TypeA
public const A = 50;
// 程序集B,引用了程序集A
Console.WriteLine(TypeA.A);
程序集B生成应用程序B之后, 用IL代码看出, 常量值直接嵌入IL代码.
如果程序集A,开发人员改变常量值为1000,只重新生成程序集A,应用程序B不会被影响,常量值还是50. 只有重新编译程序集B并生成才可以.
如果希望在运行时从一个程序集中提取另一个程序集的值,不应该使用常量,而应该使用readonly
字段.
字段
字段
是一种数据成员,其中容纳了一个值类型的实例或者对一个引用类型的引用.
volatile 翻译为可变的, 其实它是短暂存在,易变的意思,因为可能有多个线程都想对这个字段进行修改. “易变/易失” 翻译更佳.
CLR支持
- 类型字段.(静态)
- 容纳字段数据所需的动态内存是在
类型对象
中创建时分配的.类型对象
是在类型加载到AppDomain
时创建的.
- 容纳字段数据所需的动态内存是在
类型对象通常是在引用了 该类型的任何方法首次进行JIT编译 的时候,将该类型加载到AppDomain中.
- 实例字段.(非静态)
- 容纳字段数据所需的动态内存是在 构造类型的实例时分配 的.
字段存储在动态内存中,它们的值在运行时才能获取.(字段还解决了常量存在版本控制问题). 此外字段可以使任何数据类型,不像常量仅仅局限于编译器内置的基元类型.
CLR支持readonly字段
和read/write字段
.
readonly字段
- 只能在构造器方法中写入.(构造器方法只调用一次,即对象首次创建的时.)
- 编译器和验证机制确保readonly字段不会被构造器以外的任何方法写入.
- 可以利用反射来修改readonly字段(非常规手段反射,操作内存).
更改上一段代码,程序集B的代码不需要改,这样如果修改程序集A的值为1000,程序集B不需要重新编译就能获取到新值.
// 程序集A 类TypeA
// 字段和类型关联需要用static关键字
public static readonly A = 50;
当某个字段是引用类型,并且该字段被标记为readonly时,不可变的是引用
,而不是字段引用的对象
.
public static readonly Char[] cc = new Char[]{'a','b','c'};
...
// 合法的,可以通过编译
cc[0] = 'A';
// 非法的,无法通过编译,因为不能让cc引用别的东西
cc = new Char[]{'x','y','z'};
如何定义一个与类型本身关联的字段
// 这是一个静态的readonly字段; 在运行时对它初始化
// 它的值会被计算并存储到内存中
public static readonly Random s_random = new Random();
// 静态的可读可写字段
private static Int32 s_numberOfWrites = 0;
// 实例只读的字段
public readonly String Pathname = "Untitled";
// 实例可读可写的字段
private System.IO.FileStream m_fs;
public SomeType(String pathname)
{
// 修改只读字段Pathname
// 在构造器中可以这样做
this.Pathname = pathname;
}
上述代码许多字段都是内联初始化
的. C#允许使用这种内联初始化语法来初始化类的常量
,可读可写字段
和readonly字段
.
c#其实是在构造器对字段进行初始化的, 字段的内联初始化只是一种语法上的简化.(有些情况用内联语法需要考虑性能问题)
内联初始化: 代码中直接赋值来初始化,而不是将对构造器的调用写出来.