只读字段以及init关键字

113 阅读3分钟

1.const 与 readonly 字段

  • 核心区别

    • const:编译时常量,隐式静态,只能通过类名访问。必须在声明时赋值,仅支持基本类型(如数值、字符串)。
    • readonly:运行时常量,属于实例级别,可在声明或构造函数中赋值,支持任意类型。
  • 下例中的name 就是一个常量(const),agelength 是只读字段(readonly),name需要使用类名来被访问,因为常量属于类本身而不是对象实例。

Person p = new Person(25, 100);
Console.WriteLine($"age={p.age}, name={Person.name}, length={p.length}");
​
class Person
{
    public const string name = "Lisa";  // 类级别常量
    public readonly int age;            // 实例只读字段
    public readonly double length = 18.8; // 声明时赋值
​
    public Person(int age, double length)
    {
        this.age = age;
        this.length = length; // 构造函数中赋值
    }
}
  • 优点

    • 直接存储数据readonly 字段无属性访问逻辑,访问性能略高(差异通常微小)。

    缺点

    • 暴露实现细节public readonly 直接暴露字段,无法添加访问控制逻辑。
    • 无法使用对象初始化器:只能在构造函数或声明时赋值。

2.只读属性(仅含 get 访问器)

通过设置只含get访问器来实现只读。

public class Rectangle
{
    private double _width;
    private double _height;
​
    public double Area => _width * _height; // 计算属性
​
    public Rectangle(double width, double height)
    {
        _width = width;
        _height = height;
    }
}
  • 优点

    • 良好封装性:隐藏内部实现,可添加验证逻辑。
    • 灵活性:支持计算属性或延迟加载。

    缺点

    • 语法稍复杂:需额外定义字段和属性。
    • 潜在性能开销:计算属性每次访问需重新计算,但简单返回字段的属性通常无显著开销。

3.Private set 访问器

  • 核心特性

    • 外部只读,内部可写:属性对外部代码是只读的,但类内部(包括构造函数和其他方法)可以修改其值。
    • 灵活性:允许在类的生命周期内多次修改属性值,但对外保持只读。
Person person = new Person("Alice");
Console.WriteLine(person.Name); // 输出 "Alice"
person.Rename("Bob");
Console.WriteLine(person.Name); // 输出 "Bob"// person.Name = "Charlie"; // 编译错误:外部无法直接修改
public class Person
{
    public string Name { get; private set; }
​
    public Person(string name)
    {
        Name = name; // 构造函数内赋值
    }
​
    public void Rename(string newName)
    {
        Name = newName; // 类内部方法修改值
    }
}
  • 优点

    • 可控的封装性:对外隐藏写操作,同时保留类内部修改属性的灵活性。
    • 支持延迟初始化:可在类的方法中按需初始化或更新属性值。
    • 兼容旧版本:无版本限制(C# 1.0+ 均支持)。
  • 缺点

    • 非完全不可变:属性值可能在对象生命周期内被类内部代码多次修改,不适合严格不可变场景。
    • 线程安全性问题:若多线程环境下类内部可能修改属性值,需自行加锁。

4.init 关键字(C# 9.0+)

  • 核心特性

    • 允许在对象初始化期间(构造函数或初始化器)赋值,之后不可修改。
    • 结合 get 访问器实现不可变性。
Person p = new Person() { Name = "Bob" }; // 对象初始化器赋值
Console.WriteLine(p.Name); // 输出 "Bob"class Person
{
    public string Name { get; init; } = "Lisa"; // 声明时赋值
​
    public Person()
    {
        Name = "Andy"; // 构造函数中赋值(会被初始化器覆盖)
    }
}
  • 优点

    • 不可变性:初始化后值不可变,适合构建安全数据模型。
    • 对象初始化器支持:简化代码,提升可读性。

5.总结

特性readonly 字段仅含 get 的属性init 关键字private set 属性
赋值时机声明时或构造函数中声明时或构造函数中声明时、构造函数或初始化器类内部任何时机
外部可读性是(若 public
外部可写性仅初始化器
内部可写性否(构造函数外)否(需通过私有字段)否(初始化后不可变)
适用场景完全不可变的字段计算属性或严格不可变初始化后不可变的属性外部只读、内部可变的属性