You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多
文档控制台
免费开始使用

C#中为何readonly关键字仅适用于结构体的属性?

为什么C#中结构体允许readonly属性,而类和记录不行?

首先看示例代码:

public class MyClass
{
    public readonly List<int> Value { get; } = [];  // 错误:属性不能是'readonly'
}

public record MyRecord
{
    public readonly string Name { get; init; }  // 错误:属性不能是'readonly'
}

public struct MyStruct
{
    public readonly string Name { get; init; }  // 正常
}

如上述代码所示,不管属性的访问器是{ get; set; }{ get; }还是{ get; init; }readonly修饰符都无法用于类或记录的属性,但应用在结构体的属性上却不会报错(哪怕看起来有点冗余)。

已有其他讨论解释了类和记录不允许readonly属性的原因,但我想知道为什么结构体可以这么做?这和结构体是值类型有关吗?

编辑:这个问题的部分内容曾在相关文章中讨论过,但那些内容主要聚焦于结构体readonly方法的作用,并没有直接解释readonly属性存在的合理性。

我发现readonly属性在编译器生成的底层代码中,会被转换成由readonly字段支持的常规属性,但不确定这是不是唯一差异。同时我还有疑问:为什么这种机制不能用在类和记录上?毕竟在类里我们可以手动实现「属性+readonly后备字段」的组合。


核心原因:值类型的特性与语义一致性

1. 值类型的复制特性与编译期约束

结构体是值类型,传递或赋值时会发生完整复制。给结构体属性加readonly后,编译器会做两件关键事:

  • 将自动实现属性的后备字段标记为readonly
  • 强制属性的getter不会修改结构体的任何字段

这相当于编译器帮你自动完成了「手动声明readonly后备字段+属性」的工作,既减少了重复代码,又能在编译期强化不可变性约束——避免开发者不小心在属性访问器里修改结构体状态。

而类是引用类型,readonly修饰符如果应用在属性上会带来语义歧义:是指属性的引用不能变(类似readonly字段的语义),还是指属性对应的后备字段是readonly?C#设计时为了避免这种歧义,直接禁止类和记录的属性使用readonly修饰符——毕竟手动声明readonly后备字段的方式已经足够清晰。

2. 只读结构体实例的调用限制

当结构体实例被标记为readonly(比如作为readonly字段、带有in修饰符的方法参数)时,编译器只允许调用该结构体的readonly成员(方法或属性),因为能保证这些成员不会修改实例状态。如果没有readonly属性的语法,你需要手动编写readonly后备字段和对应属性,代码会更繁琐。readonly属性让这种不可变性声明更简洁统一。

3. 语法一致性的设计考量

C# 8.0引入结构体的readonly成员特性时,涵盖了方法和属性。允许readonly属性是为了语法一致性——开发者可以用统一的方式标记结构体中不会修改状态的成员,无需区分方法还是属性,让代码的意图更清晰。

关于类不支持的补充

你提到类可以手动实现「属性+readonly后备字段」,这正是C#不支持类的readonly属性的原因:手动实现的方式已经足够明确,不需要额外的语法糖。而结构体的readonly属性除了简化代码,还能配合值类型的复制特性带来编译期优化(比如避免不必要的实例复制),这是类不需要的。


内容的提问来源于stack exchange,提问作者Leonel Hou

火山引擎 最新活动