c# const 和 readonly 的区别

19次阅读

const值编译时内联,更新需全量重编译;readonly运行时初始化,支持复杂类型和构造函数赋值,Static readonly适用于跨程序集安全更新。

c# const 和 readonly 的区别

const 只能在编译时确定值,改了就得全量重编译

如果你把一个配置写成 const String ApiUrl = "https://api.example.com";,那这个字符串不是“存在内存里”,而是被 C# 编译器直接**替换进所有调用它的 IL 代码中**。这意味着:一旦你更新了这个常量、发布新版本的类库,但调用方没重新编译——它还在用旧地址。

  • 常见错误现象:ApiUrl 在类库中已改成 "https://new-api.example.com",但老客户端仍请求旧地址,且日志/调试都看不出问题
  • 适用场景:数学常量(const double Pi = 3.14159;)、固定协议标识(const string ContentTypejson = "application/json";)这类真正“永不变”的值
  • 不能用于需要运行时计算的值,比如 DateTime.Now.ToString()Environment.GetFolderPath(...) —— 编译器直接报错

readonly 支持运行时赋值,能用在构造函数和复杂类型上

readonly 字段不是“编译期硬编码”,而是在对象创建过程中(声明时或构造函数里)一次性赋值,之后禁止修改。它不挑类型,也不要求“编译期可算”。

  • 支持任意类型:数组、自定义类、DateTimeGuid、甚至 HttpClient 实例(只要确保只初始化一次)
  • 可以是实例级或静态级:readonly string InstanceId 每个对象不同;static readonly Guid AppId 全局一份
  • 常见错误:在普通方法里试图赋值 this.Timeout = 30;编译错误 CS0198:“无法对只读字段赋值”
public class ServiceClient {     public readonly int Timeout;     public readonly DateTime CreatedAt;     public readonly HttpClient Http; 
public ServiceClient(int timeout) {     Timeout = timeout;              // ✅ 构造函数中赋值 OK     CreatedAt = DateTime.UtcNow;   // ✅ DateTime 支持     Http = new HttpClient();       // ✅ 引用类型也 OK }

}

const 隐含 static,readonly 默认是实例级

你写 const int MaxRetries = 3;,它天然就是类级别的,只能通过 MyClass.MaxRetries 访问,不能用实例去点 —— 即使写了 var x = new MyClass(); x.MaxRetries,编译器也会报错。

  • readonly 字段默认属于实例:每个对象都有自己的一份副本(比如缓存路径、用户 ID 等)
  • 要让它变成类级别?加 static readonly,例如:static readonly string ConfigPath = Path.Combine(AppContext.BaseDirectory, "config.json");
  • 不能写 static const —— 语法错误,因为 const 已经是静态的了

选哪个?看三件事:能不能编译期确定、类型是否受限、要不要跨程序集安全更新

这不是风格偏好,而是行为差异带来的实际后果。尤其当你在写 NuGet 包或基础类库时,选错会埋坑。

  • 值是 int/string/enum,且确认永远不变 → 用 const(性能略高,无内存开销)
  • 值依赖构造参数、环境变量配置文件、或类型是 List/DateTime/Object → 必须用 readonly
  • 要发布给外部项目用?优先选 static readonly 而非 const,否则别人升级你的包却没重编译,就会拿到过期值

容易被忽略的一点:即使你用 readonly,也不能阻止反射强行修改(FieldInfo.SetValue(...)),但它至少守住编译期和常规运行时的契约。而 const 的“不可变”是编译器强制内联的结果,连反射都改不了——因为根本没字段存在。

text=ZqhQzanResources