深入解析 C# 中 const 与 readonly 的核心区别

在 C# 编程中,const 与 readonly 经常被统称为“常量”,但二者在初始化规则、编译/运行时行为、IL 生成方式、版本兼容性、引用类型语义等方面存在本质差异。误用不仅可能引入隐蔽的逻辑错误,还会带来库升级后的版本陷阱


一、初始化位置:编译时强约束 vs 运行时一次性赋值

1️⃣ const必须声明即赋值(编译期确定)

  • 必须在声明处赋值
  • 值在编译阶段就已确定
  • 任何位置都不能再次赋值(包括构造函数)
public class ConstantDemo
{
    public const int MaxRetryCount = 3;
    public const string DefaultTitle = "C#常量解析";

    // ❌ 编译错误:声明时未赋值
    // public const double Pi;

    // ❌ 编译错误:不能在构造函数中修改
    // public ConstantDemo()
    // {
    //     MaxRetryCount = 5;
    // }
}

📌 结论const 是“声明即终值”的编译期常量


2️⃣ readonly声明时或构造函数中赋值(运行期确定)

  • 可以在声明处赋值
  • 也可以在 实例构造函数 / 静态构造函数 中赋值
  • 每个字段只允许赋值一次
public class ReadonlyDemo
{
    // 声明时赋值
    public readonly int MinAge = 18;

    // 构造函数中赋值
    public readonly int UserId;
    public ReadonlyDemo(int userId)
    {
        UserId = userId;
    }

    // 静态 readonly:在静态构造函数中赋值
    public static readonly string Version;
    static ReadonlyDemo()
    {
        Version = "1.0.1";
    }
}

📌 结论readonly 是“构造期冻结”的运行时常量。


二、修饰对象范围:字段 + 局部变量 vs 仅字段

const:字段 & 局部变量都支持

public class ConstScopeDemo
{
    public const int GlobalConst = 100;

    public void LocalConstDemo()
    {
        const string LocalMsg = "局部常量";
        Console.WriteLine(LocalMsg);
    }
}

readonly只能修饰字段

public class ReadonlyScopeDemo
{
    public readonly int FieldReadonly = 50;

    public void LocalReadonlyError()
    {
        // ❌ 编译错误:readonly 不能修饰局部变量
        // readonly int x = 10;
    }
}

三、编译期 vs 运行期:这是最本质的差异 ⭐⭐⭐

1️⃣ const值被直接“内联”到 IL 中

public const int ConstValue = 10;

public void UseConst()
{
    int a = ConstValue;
}

IL 行为本质

ldc.i4.s 10   // 直接压栈常量 10

⚠️ 重大隐患(版本陷阱)

  • 修改类库中的 const 值
  • 但 引用方未重新编译
  • 引用方仍然使用旧值 ❌

2️⃣ readonly始终通过字段访问(运行期绑定)

public readonly int ReadonlyValue;

public ReadonlyDemo()
{
    ReadonlyValue = 20;
}

public void UseReadonly()
{
    int b = ReadonlyValue;
}

IL 行为本质

ldfld int32 ReadonlyValue

✅ 修改值后,只需重新编译类库即可,调用方无需重新编译


四、静态语义:隐式静态 vs 显式静态

const天然 static,且禁止显式声明

public class ConstStaticDemo
{
    public const int ConstStatic = 10;

    // ❌ 编译错误
    // public static const int Invalid = 20;
}

调用方式:

int x = ConstStaticDemo.ConstStatic;

readonly:默认实例级,静态需显式声明

public class ReadonlyStaticDemo
{
    public readonly int InstanceReadonly = 100;
    public static readonly int StaticReadonly = 200;
}

五、引用类型语义:值不可变 vs 引用不可变

const:仅支持 string / null

public class ConstReferenceDemo
{
    public const string ConstString = "Hello";
    public const object ConstNull = null;

    // ❌ 编译错误
    // public const List<int> ConstList = new List<int>();
}

原因:

  • const 需要 编译期确定值
  • 除 string 外,引用对象无法编译期确定

readonly:支持任意引用类型(但仅锁引用)

public class ReadonlyReferenceDemo
{
    public readonly List<int> Numbers = new() { 1, 2, 3 };

    public void Modify()
    {
        Numbers.Add(4);   // ✅ 合法

        // ❌ 编译错误:不能重新赋值
        // Numbers = new List<int>();
    }
}

⚠️ readonly ≠ 不可变对象

  • 锁的是 引用地址
  • 不是对象内容

六、完整对比速查表

维度constreadonly
初始化时机编译期运行期
赋值位置仅声明处声明 / 构造函数
修饰对象字段 + 局部变量仅字段
静态特性默认 static默认实例级
IL 行为内联常量字段访问
引用类型string / null任意引用类型
版本安全❌ 易出问题✅ 安全

七、工程化使用建议(非常重要)

✅ 优先使用 const 的场景

  • 数学常量(PIE
  • 永不变化的协议值、枚举值
  • 不会被类库外部依赖引用的内部常量
public const int MaxDays = 7;

✅ 推荐使用 readonly 的场景(真实项目更常见)

  • 类库对外暴露的“常量”
  • 配置读取、构造参数注入
  • 引用类型常量(集合、策略对象等)
public static readonly string ConnectionString;
static DbConfig()
{
    ConnectionString = LoadFromConfig();
}

八、一句话记忆法(面试 & 实战)

const 是“编译期写死的字面量”
readonly 是“构造期冻结的字段”


结语

const 与 readonly 的差异,本质并不在“能不能改”,而在于:

  • 值是在什么时候决定的?(编译期 vs 运行期)
  • 是否参与 IL 内联?
  • 是否影响程序集版本兼容?

在真实工程中:

🔥 99% 对外暴露的“常量”,都应该使用 readonly 而不是 const

理解这一点,你就已经超过了大多数只停留在语法层面的 C# 开发者。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

bugcome_com

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值