在C#中,readonly
特性用于声明一个只读字段。一个只读字段可以在声明时或在构造函数内部进行初始化,一旦被赋予了初始值,它就不能被改变。
使用
下面是使用 readonly
特性的基本语法:
public readonly int MyField;
您也可以在构造函数中初始化只读字段,如下所示:
public class MyClass
{
public readonly int MyField;
public MyClass(int value)
{
MyField = value;
}
}
注意事项
注意:对于引用类型,readonly
修饰符仅防止修改字段本身的值,而不是防止修改字段引用的对象。换句话说,你不能更改引用字段的指向,但是可以更改该字段指向的对象的属性或方法。
垃圾收集器(GC)对 readonly
修饰的字段无特殊处理。只读性质并不影响对象的垃圾回收。
垃圾回收主要基于一个对象是否还被引用来决定是否进行回收。如果一个对象不再被任何其他对象引用,那么它就会被 GC 标记为可回收。当 GC 运行时,这些标记为可回收的对象将被清理掉,释放其占用的内存资源。
而对于 readonly
字段,它仅仅是限制了该字段的修改,也就是说一旦字段被初始化后,字段本身的值是不可以被改变的。然而这并不影响其所引用的对象在内存中的生命周期,也不影响垃圾回收的机制。
如果一个 readonly
字段所引用的对象不再被其他对象引用,那么这个对象同样会被标记为可回收,并在 GC 运行时被清理。
在C#中,readonly
关键字修饰的字段的内存分配位置取决于它是否被声明为静态(static
)。
readonly
字段是实例字段(非静态),那么它的内存将会在堆上分配,作为创建对象实例时分配的一部分。每个对象实例都有自己的readonly
实例字段副本。readonly
字段是静态字段,那么它的内存将会在高频堆(High Frequency Heap)上分配,此处用于存储所有的静态数据。所有实例共享一个readonly
静态字段。无论是静态还是非静态的 readonly
字段,都只能在声明时或在相应的构造函数中初始化。对于静态 readonly
字段,这通常发生在静态构造函数或者第一次引用类之前。对于非静态 readonly
字段,它们在实例构造函数中初始化。
下面是一个代码示例:
public class MyClass
{
public readonly int InstanceField; // 在堆上分配内存
public static readonly int StaticField; // 在高频堆上分配内存
public MyClass(int value)
{
InstanceField = value;
}
// 静态构造函数
static MyClass()
{
StaticField = 10;
}
}
readonly
关键字在C#中表示一旦字段被初始化,它的值就不能再被改变。这种不可变性在某种程度上可以提高多线程环境下的线程安全性。
对于值类型(如int
、bool
、double
等)或不可变的引用类型(如string
),readonly
字段是绝对线程安全的,因为他们的状态一旦初始化就无法改变。
但是,对于可变的引用类型(如列表、字典或自定义类),虽然你无法改变readonly
字段本身引用的对象,但你仍然可以修改该对象的内部状态。例如,你可以向一个readonly
的列表中添加项目。如果不同的线程试图同时修改这个列表,那么可能会遇到线程安全问题。
以下是一个例子,解释了以上的概念:
public class MyClass
{
public readonly List<int> MyList = new List<int>(); // 可变引用类型
// ...其他代码...
public void AddItem(int item)
{
// 需要保证线程安全,因为MyList是可变的
lock (MyList)
{
MyList.Add(item);
}
}
}
readonly
只能保证字段本身不会被改变,而不能保证其引用的对象的状态不被改变。在处理可变的引用类型时,还需要采取额外的同步措施以确保线程安全。
无论字段是否被 readonly
修饰,对象的传递方式(引用或值)都取决于其类型。