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

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


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

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

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

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

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

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


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

  • 可以在声明处赋值
  • 也可以在 实例构造函数 / 静态构造函数 中赋值
  • 每个字段只允许赋值一次
cs 复制代码
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:字段 & 局部变量都支持

cs 复制代码
public class ConstScopeDemo
{
    public const int GlobalConst = 100;

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

readonly只能修饰字段

cs 复制代码
public class ReadonlyScopeDemo
{
    public readonly int FieldReadonly = 50;

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

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

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

cs 复制代码
public const int ConstValue = 10;

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

IL 行为本质

cs 复制代码
ldc.i4.s 10   // 直接压栈常量 10

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

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

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

cs 复制代码
public readonly int ReadonlyValue;

public ReadonlyDemo()
{
    ReadonlyValue = 20;
}

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

IL 行为本质

cs 复制代码
ldfld int32 ReadonlyValue

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


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

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

cs 复制代码
public class ConstStaticDemo
{
    public const int ConstStatic = 10;

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

调用方式:

cs 复制代码
int x = ConstStaticDemo.ConstStatic;

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

cs 复制代码
public class ReadonlyStaticDemo
{
    public readonly int InstanceReadonly = 100;
    public static readonly int StaticReadonly = 200;
}

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

const:仅支持 string / null

cs 复制代码
public class ConstReferenceDemo
{
    public const string ConstString = "Hello";
    public const object ConstNull = null;

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

原因:

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

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

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

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

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

⚠️ readonly ≠ 不可变对象

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

六、完整对比速查表

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

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

✅ 优先使用 const 的场景

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

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

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

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

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


结语

constreadonly 的差异,本质并不在"能不能改",而在于:

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

在真实工程中:

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

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

相关推荐
kylezhao20192 小时前
工业机器视觉基础认知
计算机视觉·c#·visionpro
水龙吟啸3 小时前
项目设计与开发:智慧校园食堂系统
python·机器学习·前端框架·c#·团队开发·visual studio·数据库系统
flysh053 小时前
C#语言基础知识要点
开发语言·c#
闻缺陷则喜何志丹4 小时前
【三维建模】三维建模基础一
c#·计算几何·cad·三维建模·布尔运算·切点
我是唐青枫15 小时前
深入理解 C#.NET Interlocked.Increment:原子操作的核心
c#·.net
yue00815 小时前
C# 字符串倒序
开发语言·c#
ejjdhdjdjdjdjjsl18 小时前
C#类型转换与异常处理全解析
开发语言·c#
我是唐青枫20 小时前
深入理解 C#.NET Parallel:并行编程的正确打开方式
开发语言·c#·.net
yue00820 小时前
C# ASCII和字符串相互转换
c#