const声明的编译时常量必须在声明时用编译期常量初始化,仅支持基元类型、枚举或null;static readonly适用于运行时确定且后续只读的场景,支持任意类型并在运行时解析,跨程序集更新更安全。

const 声明的编译时常量必须初始化
在 C# 中,const 修饰的字段是编译时常量,它要求声明时就必须用编译期可确定的值初始化,且类型仅限于基元类型(如 int、string、double)、枚举或 null。一旦定义,值不可变,也不能在运行时计算。
常见错误:试图用方法调用、new 表达式或非字面量表达式初始化 const —— 编译器直接报错 CS0133: The expression being assigned to 'xxx' must be constant。
-
const int MaxRetries = 3;✅ 合法 -
const string ApiUrl = "https://api.example.com";✅ 合法 -
const DateTime Now = DateTime.Now;❌ 编译失败 -
const int Count = GetDefaultCount();❌ 方法调用不被允许
static readonly 适合运行时只读的场景
当值需要在运行时确定(比如从配置、环境变量或构造函数中获取),但之后不允许修改,就该用 static readonly。它支持任意类型,初始化可在声明处或静态构造函数中完成。
注意:虽然 readonly 字段本身不能重新赋值,但如果它是引用类型(如 List<int>),其内部状态仍可变 —— 这常被误认为“常量被改了”。
-
static readonly string ConnectionString = ConfigurationManager.ConnectionStrings["Main"].ConnectionString;✅ 典型配置读取 -
static readonly List<string> SupportedFormats = new() { "json", "xml" };✅ 可声明后添加元素(SupportedFormats.Add("yaml")是合法的) -
static readonly HttpClient Http = new();✅ 实例化没问题,但不能写成Http = new HttpClient()(重复赋值)
public static class 封装常量组更利于维护
零散的 const 或 static readonly 容易散落在各处,推荐按语义聚合成静态类,比如 ApiConstants、DbConstants。这样既避免命名污染全局作用域,又方便查找和复用。
别把所有常量塞进一个大类里 —— 模块耦合高、变更风险大。例如数据库连接字符串和 HTTP 超时时间本就不该混在一个类里。
- 定义:
public static class ApiConstants { public const string BaseUrl = "https://api.example.com/v1"; public static readonly TimeSpan Timeout = TimeSpan.FromSeconds(30); } - 使用:
var url = ApiConstants.BaseUrl + "/users"; - 若某常量只在单个类内使用,优先定义为
private const,而非暴露到公共静态类
const 和 static readonly 的性能与跨程序集影响
const 值会在编译时“内联”到所有引用它的调用方代码中;而 static readonly 是运行时读取。这意味着:如果更新了一个 const 值但没重新编译所有依赖程序集,旧程序仍会用老值 —— 这是隐蔽的版本陷阱。
因此,对外发布的 SDK 或 NuGet 包中,慎用 const 公共字段;涉及可能变更的配置项(如 API 版本号、默认重试次数),一律改用 static readonly。
- 假设
public const string Version = "1.2";在库 A 中,App B 引用了 A 并编译过。之后 A 改成"1.3"但 B 不重编译 → B 运行时仍显示"1.2" -
public static readonly string Version = "1.2";则只要更新并部署 A 的新 DLL,B 就能读到新值 - 性能差异极小,别为了“快一点”牺牲可维护性
实际项目里最常被忽略的是常量的生命周期管理 —— 你以为定义一次就一劳永逸,但跨程序集发布、热更新、配置中心对接这些场景,会让 const 成为故障放大器。


















