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字段不是“编译期硬编码”,而是在对象创建过程中(声明时或构造函数里)一次性赋值,之后禁止修改。它不挑类型,也不要求“编译期可算”。 支持任意类型:数组、自定义类、
DateTime、
Guid、甚至
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;
<pre class='brush:php;toolbar:false;'>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<t></t>/
DateTime/
object→ 必须用
readonly要发布给外部项目用?优先选
static readonly而非
const,否则别人升级你的包却没重编译,就会拿到过期值
容易被忽略的一点:即使你用
readonly,也不能阻止反射强行修改(
FieldInfo.SetValue(...)),但它至少守住编译期和常规运行时的契约。而
const的“不可变”是编译器强制内联的结果,连反射都改不了——因为根本没字段存在。
