在 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≠ 不可变对象
- 锁的是 引用地址
- 不是对象内容
六、完整对比速查表
| 维度 | const | readonly |
|---|---|---|
| 初始化时机 | 编译期 | 运行期 |
| 赋值位置 | 仅声明处 | 声明 / 构造函数 |
| 修饰对象 | 字段 + 局部变量 | 仅字段 |
| 静态特性 | 默认 static | 默认实例级 |
| IL 行为 | 内联常量 | 字段访问 |
| 引用类型 | string / null | 任意引用类型 |
| 版本安全 | ❌ 易出问题 | ✅ 安全 |
七、工程化使用建议(非常重要)
✅ 优先使用 const 的场景
- 数学常量(
PI、E) - 永不变化的协议值、枚举值
- 不会被类库外部依赖引用的内部常量
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# 开发者。
1545

被折叠的 条评论
为什么被折叠?



