浅谈C# 中 const 与 readonly的核心区别

 更新时间:2026年05月10日 10:22:03   作者:bugcome_com  
在 C# 编程中,const 与 readonly 经常被统称为“常量”,但二者在初始化规则、编译/运行时行为、IL 生成方式、版本兼容性、引用类型语义等方面存在本质差异,下面就来详细的介绍一下,感兴趣的可以了解一下

在 C# 编程中,const 与 readonly 经常被统称为“常量”,但二者在初始化规则、编译/运行时行为、IL 生成方式、版本兼容性、引用类型语义等方面存在本质差异。误用不仅可能引入隐蔽的逻辑错误,还会带来库升级后的版本陷阱

一、初始化位置:编译时强约束 vs 运行时一次性赋值

1️⃣const:必须声明即赋值(编译期确定)

  • 必须在声明处赋值
  • 值在编译阶段就已确定
  • 任何位置都不能再次赋值(包括构造函数)
public class ConstantDemo
{
    public const int MaxRetryCount = 3;
    public const string DefaultTitle = "C#常量解析";

    // ❌ 编译错误:声明时未赋值
    // public const double Pi;

    // ❌ 编译错误:不能在构造函数中修改
    // public ConstantDemo()
    // {
    //     MaxRetryCount = 5;
    // }
}

📌 结论:const 是“声明即终值”的编译期常量

2️⃣readonly:声明时或构造函数中赋值(运行期确定)

  • 可以在声明处赋值
  • 也可以在 实例构造函数 / 静态构造函数 中赋值
  • 每个字段只允许赋值一次
public class ReadonlyDemo
{
    // 声明时赋值
    public readonly int MinAge = 18;

    // 构造函数中赋值
    public readonly int UserId;
    public ReadonlyDemo(int userId)
    {
        UserId = userId;
    }

    // 静态 readonly:在静态构造函数中赋值
    public static readonly string Version;
    static ReadonlyDemo()
    {
        Version = "1.0.1";
    }
}

📌 结论:readonly 是“构造期冻结”的运行时常量。

二、修饰对象范围:字段 + 局部变量 vs 仅字段

const:字段 & 局部变量都支持

public class ConstScopeDemo
{
    public const int GlobalConst = 100;

    public void LocalConstDemo()
    {
        const string LocalMsg = "局部常量";
        Console.WriteLine(LocalMsg);
    }
}

readonly:只能修饰字段

public class ReadonlyScopeDemo
{
    public readonly int FieldReadonly = 50;

    public void LocalReadonlyError()
    {
        // ❌ 编译错误:readonly 不能修饰局部变量
        // readonly int x = 10;
    }
}

三、编译期 vs 运行期:这是最本质的差异 ⭐⭐⭐

1️⃣const:值被直接“内联”到 IL 中

public const int ConstValue = 10;

public void UseConst()
{
    int a = ConstValue;
}

IL 行为本质

ldc.i4.s 10   // 直接压栈常量 10

⚠️ 重大隐患(版本陷阱):

  • 修改类库中的 const 值
  • 但 引用方未重新编译
  • 引用方仍然使用旧值 ❌

2️⃣readonly:始终通过字段访问(运行期绑定)

public readonly int ReadonlyValue;

public ReadonlyDemo()
{
    ReadonlyValue = 20;
}

public void UseReadonly()
{
    int b = ReadonlyValue;
}

IL 行为本质

ldfld int32 ReadonlyValue

✅ 修改值后,只需重新编译类库即可,调用方无需重新编译

四、静态语义:隐式静态 vs 显式静态

const:天然 static,且禁止显式声明

public class ConstStaticDemo
{
    public const int ConstStatic = 10;

    // ❌ 编译错误
    // public static const int Invalid = 20;
}

调用方式:

int x = ConstStaticDemo.ConstStatic;

readonly:默认实例级,静态需显式声明

public class ReadonlyStaticDemo
{
    public readonly int InstanceReadonly = 100;
    public static readonly int StaticReadonly = 200;
}

五、引用类型语义:值不可变 vs 引用不可变

const:仅支持string/null

public class ConstReferenceDemo
{
    public const string ConstString = "Hello";
    public const object ConstNull = null;

    // ❌ 编译错误
    // public const List<int> ConstList = new List<int>();
}

原因:

  • const 需要 编译期确定值
  • 除 string 外,引用对象无法编译期确定

readonly:支持任意引用类型(但仅锁引用)

public class ReadonlyReferenceDemo
{
    public readonly List<int> Numbers = new() { 1, 2, 3 };

    public void Modify()
    {
        Numbers.Add(4);   // ✅ 合法

        // ❌ 编译错误:不能重新赋值
        // Numbers = new List<int>();
    }
}

⚠️ readonly ≠ 不可变对象

  • 锁的是 引用地址
  • 不是对象内容

六、完整对比速查表

维度constreadonly
初始化时机编译期运行期
赋值位置仅声明处声明 / 构造函数
修饰对象字段 + 局部变量仅字段
静态特性默认 static默认实例级
IL 行为内联常量字段访问
引用类型string / null任意引用类型
版本安全❌ 易出问题✅ 安全

七、工程化使用建议(非常重要)

✅ 优先使用const的场景

  • 数学常量(PIE
  • 永不变化的协议值、枚举值
  • 不会被类库外部依赖引用的内部常量
public const int MaxDays = 7;

✅ 推荐使用readonly的场景(真实项目更常见)

  • 类库对外暴露的“常量”
  • 配置读取、构造参数注入
  • 引用类型常量(集合、策略对象等)
public static readonly string ConnectionString;
static DbConfig()
{
    ConnectionString = LoadFromConfig();
}

八、一句话记忆法(面试 & 实战)

const 是“编译期写死的字面量”
readonly 是“构造期冻结的字段”

结语

const 与 readonly 的差异,本质并不在“能不能改”,而在于:

  • 值是在什么时候决定的?(编译期 vs 运行期)
  • 是否参与 IL 内联?
  • 是否影响程序集版本兼容?

在真实工程中:

🔥 99% 对外暴露的“常量”,都应该使用 readonly 而不是 const。

理解这一点,你就已经超过了大多数只停留在语法层面的 C# 开发者。

到此这篇关于浅谈C# 中 const 与 readonly的核心区别的文章就介绍到这了,更多相关C# const readonly内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 深入分析C# Task

    深入分析C# Task

    这篇文章主要介绍了C# Task的相关资料,文中讲解非常细致,代码帮助大家更好的理解和学习C# Task的相关知识,感兴趣的朋友可以了解下
    2020-08-08
  • C#判断一个字符串是否包含另一个字符串的方法

    C#判断一个字符串是否包含另一个字符串的方法

    这篇文章主要介绍了C#判断一个字符串是否包含另一个字符串的方法,涉及C#中IndexOf方法的使用技巧,非常简单实用,需要的朋友可以参考下
    2015-04-04
  • C#设置MDI子窗体只能弹出一个的方法

    C#设置MDI子窗体只能弹出一个的方法

    这篇文章主要介绍了C#设置MDI子窗体只能弹出一个的方法,很实用的技巧,需要的朋友可以参考下
    2014-08-08
  • C#简单生成缩略图的方法

    C#简单生成缩略图的方法

    这篇文章主要介绍了C#简单生成缩略图的方法,涉及C#操作图片的技巧,非常具有实用价值,需要的朋友可以参考下
    2015-03-03
  • C#中存储当前项目设置的步骤详解

    C#中存储当前项目设置的步骤详解

    我们在编写软件的时候总有一些配置数据需要保存,比如用户选择的偏好设置,又如软件所用到的数据库文件等,我们有很多中方式都可以保存,本文给大家介绍了C#中存储当前项目设置的详细步骤,需要的朋友可以参考下
    2026-02-02
  • C# 泛型集合的自定义类型排序的实现

    C# 泛型集合的自定义类型排序的实现

    这篇文章主要介绍了C# 泛型集合的自定义类型排序的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-11-11
  • C# $字符串插值的使用

    C# $字符串插值的使用

    本文介绍了C#中的字符串插值功能,详细介绍了使用$符号的实现方式,文中通过示例代码介绍的非常详细,需要的朋友们下面随着小编来一起学习学习吧
    2025-07-07
  • C# winformTextBox 键盘监听方式

    C# winformTextBox 键盘监听方式

    这篇文章主要介绍了C# winformTextBox 键盘监听方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-04-04
  • C#中out与ref的区别实例解析

    C#中out与ref的区别实例解析

    这篇文章主要介绍了C#中out与ref的区别实例解析,对C#初学者有不错的学习借鉴价值,需要的朋友可以参考下
    2014-08-08
  • WinForm中实现picturebox自适应图片大小的方法

    WinForm中实现picturebox自适应图片大小的方法

    这篇文章主要介绍了WinForm中实现picturebox自适应图片大小的方法,涉及pictureBox控件相关属性设置技巧,需要的朋友可以参考下
    2017-05-05

最新评论