const 和 readonly 都用于定义"不可变"的值,但它们的实现机制、使用时机和适用场景有本质区别。下面详细对比。
📋 核心区别总览
| 对比项 | const | readonly |
|---|---|---|
| 本质 | 编译时常量 | 运行时只读字段 |
| 赋值时机 | 声明时必须赋值 | 声明时或构造函数中赋值 |
| 所属层级 | 隐式静态(属于类型) | 实例级(也可加 static) |
| 值存储位置 | 嵌入到调用方代码中 | 存储在对象/类型内存中 |
| 支持类型 | 仅限基元类型、string、enum、null | 任意类型(包括对象、数组等) |
| 访问方式 | ClassName.Member | instance.Member 或 ClassName.Member |
| 版本兼容性 | 修改后需重新编译所有引用方 | 修改后无需重编译引用方 |
1. const — 编译时常量
核心特征:值在编译时确定,编译器会将常量的值直接"内联"到所有引用它的地方。
using System;
class MathConstants
{
public const double PI = 3.14159265358979;
public const string AppName = "MyApp";
public const int MaxRetry = 3;
// ❌ 编译错误!不能在构造函数中赋值
// public const double E;
// static MathConstants() { E = 2.718; }
}
class Program
{
static void Main()
{
// 直接使用,无需创建实例
Console.WriteLine($"PI = {MathConstants.PI}"); // 输出: PI = 3.14159265358979
Console.WriteLine($"App = {MathConstants.AppName}"); // 输出: App = MyApp
// 编译器实际生成的代码等价于:
// Console.WriteLine($"PI = {3.14159265358979}"); ← 值已被内联
}
}
const 的限制:
class Example
{
// ✅ 支持的类型
public const int Number = 100;
public const string Text = "hello";
public const double Pi = 3.14;
public const bool Flag = true;
public const DayOfWeek Day = DayOfWeek.Monday; // enum
public const object Null = null;
// ❌ 不支持的类型(编译错误)
// public const DateTime Today = DateTime.Now; // 非编译时常量
// public const int[] Arr = { 1, 2, 3 }; // 数组不行
// public const List<int> List = new(); // 引用类型不行
}
2. readonly — 运行时只读字段
核心特征:值在运行时确定,只能在声明时或构造函数中赋值,之后不可修改。
using System;
class Config
{
// 声明时赋值
public readonly string Version = "1.0.0";
// 构造函数中赋值(每个实例可以不同)
public readonly DateTime CreatedAt;
public readonly int InstanceId;
public Config(int id)
{
CreatedAt = DateTime.Now; // ✅ 构造函数中可以赋值
InstanceId = id;
}
public void TryModify()
{
// ❌ 编译错误!readonly 字段不能在普通方法中修改
// Version = "2.0.0";
// InstanceId = 999;
}
}
class Program
{
static void Main()
{
Config c1 = new Config(1);
Config c2 = new Config(2);
Console.WriteLine($"c1: Id={c1.InstanceId}, Time={c1.CreatedAt}");
Console.WriteLine($"c2: Id={c2.InstanceId}, Time={c2.CreatedAt}");
// 两个实例的 CreatedAt 和 InstanceId 各不相同
}
}
static readonly 的用法:
using System;
class AppConfig
{
// static readonly:整个类型共享一份,在静态构造函数中初始化
public static readonly string ConnectionString;
public static readonly DateTime StartupTime;
static AppConfig()
{
// 适合放运行时才能确定的全局配置
ConnectionString = Environment.GetEnvironmentVariable("DB_CONN") ?? "default";
StartupTime = DateTime.Now;
}
}
class Program
{
static void Main()
{
Console.WriteLine($"启动时间: {AppConfig.StartupTime}");
Console.WriteLine($"连接串: {AppConfig.ConnectionString}");
}
}
⚠️ const 的版本兼容性陷阱
这是两者最容易被忽视的区别:
// ===== 程序集 A(类库)=====
public class Constants
{
public const int Version = 1;
}
// ===== 程序集 B(引用 A)=====
Console.WriteLine(Constants.Version);
// 编译器实际生成的 IL 代码是:Console.WriteLine(1); ← 值被内联了
如果之后将程序集 A 中的 Version 改为 2,只重新编译 A 而不重编译 B,B 仍然输出 1!因为 B 的代码里已经硬编码了 1。
而 readonly 不存在这个问题,因为它是运行时从内存中读取的。
🔑 选择指南
| 场景 | 推荐 |
|---|---|
| 值是编译时就能确定的固定常量(如数学常量、枚举值) | const |
| 值需要在运行时计算(如读取配置、当前时间) | readonly |
类型是 DateTime、数组、自定义对象等复杂类型 | readonly |
| 希望每个实例有不同的只读值 | readonly(实例级) |
| 希望全局共享且运行时初始化 | static readonly |
| 值可能被未来版本修改,且不希望引用方重新编译 | readonly |
实践建议:除非你确定值永远不会变且是基元类型,否则优先使用
static readonly代替const。const的内联特性虽然性能略好,但带来的版本兼容性风险往往得不偿失。这也是很多团队编码规范中"禁止使用const,统一用static readonly"的原因。

2974

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



