const是编译期常量,声明时赋值、值被内联替换、仅限基元类型;readonly是运行时常量,可在声明或构造函数赋值、支持任意类型、跨程序集安全。

const 不能替代 readonly,反过来也一样——选错会导致编译失败、运行时行为异常,或跨程序集版本不一致。
const 必须在声明时赋值,且只能是编译期可确定的值
它不是“运行时字段”,而是编译器直接替换的字面量。一旦写成 public const int TimeoutMs = 5000;,所有调用处都会被替换成 5000,IL 中根本不存在字段访问指令。
- 支持类型严格受限:
int、string、bool、enum、double等基元类型,以及null引用(如const object NullObj = null;) -
DateTime.Now、Environment.MachineName、new List这类运行时才能确定的表达式,统统不能用于() const - 不能加
static修饰符——const隐含static,写static const int X = 1;会报错 - 局部变量也能用
const,比如方法里写const string LogPrefix = "[API]";
readonly 字段必须在构造函数或声明时赋值
它是真正的“运行时只读字段”,值存在内存中,每次访问都走字段读取逻辑,不会被内联。
- 可以是任意类型:
readonly Dictionary、Cache = new(); readonly Config Settings;都合法 - 实例字段和静态字段都支持,但静态的必须用
static readonly显式声明 - 实例
readonly字段允许不同对象拥有不同值,比如new User(101)和new User(102)的readonly int Id各自独立 - 不能修饰局部变量——
readonly int x = 5;在方法体内会编译失败
跨程序集修改 const 值,不重新编译引用方就永远看不到新值
这是最隐蔽也最容易出问题的点。假设你把 public const int MaxRetries = 3; 放在 NuGet 包里,下游项目引用后没重编译,你更新包并把值改成 5,下游代码运行时仍是 3。
- 原因:下游 IL 已固化为
ldc.i4.3,根本不查源字段 - 解决方案:只要可能被外部引用,优先用
public static readonly int MaxRetries = 3; - 例外情况:内部工具库、完全可控的单体项目,且值确定永不变更,才考虑
const
该用 static readonly 而不是 const 的典型场景
当你要的“常量”依赖运行时信息,或者类型不满足 const 要求时,static readonly 是唯一选择。
static readonly string ApiRoot = Environment.GetEnvironmentVariable("API_ROOT") ?? "https://api.example.com";static readonly Regex EmailPattern = new(@"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$");static readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(30);static readonly byte[] MagicHeader = { 0x47, 0x49, 0x46, 0x38 }; // GIF header
这些都无法用 const 实现——不是编译期常量,也不是支持的类型。强行套用只会触发编译错误,或误用 null 占位导致逻辑断裂。











