只读字段
当字段声明中含有 readonly
修饰符时,该声明所引入的字段为只读字段。给只读字段的直接赋值只能作为声明的组成部分出现,或在同一类中的实例构造函数或静态构造函数中出现。(在这些上下文中,只读字段可以被多次赋值。)准确地说,只在下列上下文中允许对 readonly
字段进行直接赋值:
- 在用于引入该字段的变量声明符中(通过添加一个变量初始值设定项)。
- 对于实例字段,在包含字段声明的类的实例构造函数中;对于静态字段,在包含字段声明的类的静态构造函数中。也只有在这些上下文中,将
readonly
字段作为out
或ref
参数传递才有效。
在其他任何上下文中,试图对 readonly
字段进行赋值或将它作为 out
或 ref
参数传递都会导致一个编译时错误。
C#常量数据与只读字段
常量数据
C#提供了const关键字来定义常量,如果我们要为应用程序定义逻辑上和某个类或结构相关的一组已知值的话,就非常有用。
假如我们创建一个MyMathClass的工具类,且需要定义一个PI值(假如是3.14),如果不希望别的开发者改变PI值,可以使用如下常量定义PI值:
1 class MyMathClass 2 { 3 //定义为常量数据 4 public const double PI=3.14; 5 } 6 class Program 7 { 8 public static void Main(string[] args) 9 { 10 //注意:因为常量数据是隐式静态的,所以只能直接在类级别上调用(MyMathClass.PI)。 11 Console.WriteLine("PI值是:{0}",MyMathClass.PI); 12 13 //错误!常量数据不能被修改。 14 MyMathClass.PI=3.15; 15 Console.ReadLine(); 16 } 17 }
注意:定义常量时必须为常量指定初始值,常量一旦定义就不能修改了。
1 class MyMathClass 2 { 3 //尝试再构造函数中给常量赋值 4 public const double PI; 5 public MyMathClass() 6 { 7 //错误! 8 PI=3.14; 9 } 10 }
在编译时必须知道常量的值!
只读字段
和常量密切联系的概念是只读字段(不要和只读属性混淆哦,只读属性指只有get块的属性)。和常量相似,只读字段不能在赋值后改变。然而,和常量不同,赋值给只读字段可以在运行时决定。因此在构造函数作用域范围内给只读字段赋值是合法的(其他地方不行!)。
1 class MyMathClass 2 { 3 //可以构造函数中为只读字段赋值,其他地方不行! 4 public readonly double PI; 5 public MyMathClass() 6 { 7 PI=3.14; 8 } 9 public void ChangePI() 10 { 11 //错误! 12 PI=3.14; 13 } 14 }
另:只读字段不是隐式静态的,要定义静态只读字段就需要使用static关键字了。
问题:请叙述const与readonly的区别。
const 关键字用于修改字段或局部变量的声明。它指定字段或局部变量的值不能被修改。常数声明引入给定类型的一个或多个常数,开心哦。const数据成员的声明式必须包含初值,且初值必须是一个常量表达式。因为它是在编译时就需要完全评估。const成员可以使用另一个const成员来初始化,前提是两者之间没有循环依赖。readonly在运行期评估赋值,使我们得以在确保“只读访问”的前提下,把object的初始化动作推迟到运行期进行。
readonly 关键字与 const 关键字不同: const 字段只能在该字段的声明中初始化。readonly 字段可以在声明或构造函数中初始化。因此,根据所使用的构造函数,readonly 字段可能具有不同的值。另外,const 字段是编译时常数,而 readonly 字段可用于运行时常数。readonly 只能在声明时或者构造函数里面初始化。
枚举与常量需要注意的一个问题
.net中枚举其实就是数值型的常量,与const类似。当我们在代码中使用枚举代表的数值或者常量时,编译器其实是将该值直接写过来,而不会在运行的时候去读取该值。下面是一个例子:
我们想建立一个类库项目,名称叫ClassLibrary1,再建立一个控制台项目,名称叫ConsoleApplication2,结构如下:
ClassLibrary1项目中Class1中的代码是:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 6 namespace ClassLibrary1 7 { 8 public class MyClass 9 { 10 public static string str1 = "str1"; 11 public const string str2 = "str2"; 12 } 13 14 public enum MyEnum 15 { 16 One = 1, 17 Two = 2, 18 Three = 3 19 } 20 }
定义了一个枚举以及一个静态字段、一个常量字段。
ConsoleApplication2项目引用A项目,代码如下:
1 using System; 2 using ClassLibrary1; 3 4 class Program 5 { 6 public static void Main(string[] args) 7 { 8 Console.WriteLine((int)MyEnum.One); 9 Console.WriteLine(MyEnum.One.ToString()); 10 11 Console.WriteLine(MyClass.str1); 12 Console.WriteLine(MyClass.str2); 13 14 Console.ReadKey(); 15 } 16 }
我们来看看Program类用Reflector工具反编译后的样子:
public class MyClass { // Fields public static string str1; public const string str2 = "str2"; // Methods static MyClass(); public MyClass(); } 注意:这里自动的添加了两个构造函数,一个静态一个非静态。
1 public static void Main(string[] args) 2 { 3 Console.WriteLine(1); 4 Console.WriteLine(MyEnum.One.ToString()); 5 Console.WriteLine(MyClass.str1); 6 Console.WriteLine("str2"); 7 Console.ReadKey(); 8 }
编译器将(int)MyEnum.One的值与常量字段str2直接硬编码写到代码中,而不是在运行期再去读取。
这样处理的后果是:如果你修改了A项目中的枚举的排列顺序或者枚举对应的值(或者改变了常量字段str2的值),比如将MyEnum.One的值2,同时不重新编译Test项目,那样运行结果还是不会变的。