C# 可变引用类型和不可变引用类型

作者:CnLg.NJ日期:2026/3/4

引用类型(class)的实例存储在托管堆上,变量保存的是对象的引用。根据对象创建后其状态是否允许被修改,可以将引用类型分为可变(Mutable)和不可变(Immutable)两类。

1. 可变引用类型

定义:对象创建后,其内部状态(字段、属性)可以被修改。
特点

  • 可以通过公开的 setter 或方法更改属性值。
  • 同一对象在不同时间点可能呈现不同状态。
  • 多线程环境下需要同步机制保证线程安全。
  • 容易产生副作用,因为多个引用可能指向同一对象,一处修改会影响所有引用。

示例StringBuilderList<T>、自定义的带有 setter 的类。

2. 不可变引用类型

定义:对象一旦创建,其内部状态就完全确定,且在整个生命周期内不会发生任何变化。
特点

  • 通常所有字段都是只读的(readonly),且只能在构造函数中初始化。
  • 不提供修改内部状态的方法(无 setter,无改变字段的方法)。
  • “修改”操作会返回一个新的实例,原实例保持不变。
  • 天然线程安全,可自由共享而无需同步。
  • 便于缓存、哈希键等场景(因为状态固定,哈希值不变)。

示例string(字符串连接等操作返回新字符串)、System.UriSystem.DateTime(虽然它是值类型,但也是不可变的)、自定义的不可变类。

3. 代码示例

3.1 不可变引用类型示例

使用 string
1string s1 = "Hello";
2string s2 = s1;          // s2  s1 引用同一个字符串对象
3Console.WriteLine($"s1: {s1}, s2: {s2}"); // 输出:Hello, Hello
4
5// 尝试“修改”s1 —— 实际上是创建了一个新字符串对象
6s1 = s1 + " World";
7Console.WriteLine($"s1: {s1}, s2: {s2}"); 
8// 输出:s1: Hello World, s2: Hello   (s2 仍然指向原字符串)
自定义不可变类 ImmutablePerson
1public class ImmutablePerson
2{
3    public string Name { get; }
4    public int Age { get; }
5
6    public ImmutablePerson(string name, int age)
7    {
8        Name = name;
9        Age = age;
10    }
11
12    // “修改”年龄:返回一个新对象
13    public ImmutablePerson WithAge(int newAge)
14    {
15        return new ImmutablePerson(this.Name, newAge);
16    }
17
18    public override string ToString() => $"{Name} ({Age})";
19}
20
21// 使用
22var p1 = new ImmutablePerson("Alice", 30);
23var p2 = p1.WithAge(31);    // p1 保持不变,p2 是新对象
24Console.WriteLine(p1);       // Alice (30)
25Console.WriteLine(p2);       // Alice (31)

3.2 可变引用类型示例

使用 StringBuilder
1StringBuilder sb1 = new StringBuilder("Hello");
2StringBuilder sb2 = sb1;          // sb2  sb1 引用同一个对象
3
4Console.WriteLine(sb1); // Hello
5Console.WriteLine(sb2); // Hello
6
7// 修改 sb1
8sb1.Append(" World");
9
10// sb2 也受到影响,因为它们指向同一对象
11Console.WriteLine(sb1); // Hello World
12Console.WriteLine(sb2); // Hello World
自定义可变类 MutablePerson
1public class MutablePerson
2{
3    public string Name { get; set; }
4    public int Age { get; set; }
5
6    public override string ToString() => $"{Name} ({Age})";
7}
8
9// 使用
10var p1 = new MutablePerson { Name = "Bob", Age = 25 };
11var p2 = p1;      // p2 引用同一个对象
12
13p1.Age = 26;      // 修改 p1
14Console.WriteLine(p1); // Bob (26)
15Console.WriteLine(p2); // Bob (26)   —— p2 也变了

4. 总结对比

特性可变引用类型不可变引用类型
状态修改可以直接修改对象内容修改操作返回新对象
线程安全通常不安全,需同步天然安全,可自由共享
内存/性能修改原地进行,无需额外分配每次修改可能分配新对象
适用场景需要频繁修改状态的场景需要共享、缓存、哈希键的场景
常见例子StringBuilder, List<T>string, System.Uri

C# 可变引用类型和不可变引用类型》 是转载文章,点击查看原文


相关推荐


【HarmonyOS】day37:React Native实战项目+关键词高亮搜索Hook
星空22232026/2/24

【HarmonyOS】React Native实战项目+关键词高亮搜索Hook 📅 更新时间:2026年2月 🎯 技术栈:HarmonyOS NEXT + React Native 0.72.5 + TypeScript ⏱️ 阅读时间:约15分钟 前言 进入2026年,移动端开发格局已发生根本性变化。随着HarmonyOS NEXT彻底剥离AOSP,开发者面临着Android、iOS、HarmonyOS三足鼎立的局面。如何用一套代码高效覆盖三大平台? 本文将带你从零开


超详细的云服务部署 OpenClaw 并接入飞书全流程,别再趟坑了
vortesnail2026/2/16

先讲点题外话 大概是 2015 年,我在大学寝室看了一部电影《她》,讲的是一个人与人工智能相爱的科幻爱情电影。 电影中的“女主”是斯嘉丽配音的人工智能操作系统 OS1 ,她可以深入了解、分析并理解你的生活,通过每日的经历不断成长和完善,不仅能够帮你实打实干事,还能够理解环境和用户的情绪,从而不断地进化成一个你越来越信任和依赖的伙伴。 那时候觉得这种形态的产品终究是会来的,想着 50 岁左右应该人工智能能发展到这种程度,但 2026 年的今天,已经能看到这种产品的雏形了!并且这回我坚定相信,今年


山野的风,城市的窗:一位拾粪爷爷与我的时代之问
修己xj2026/2/7

一、黑白影像中的昨日 今天在滑看手机时,一张九十年代的老照片忽然映入眼帘:一位穿着粗布衣裳的老人,背着一只粪筐,正弯着腰在路上拾粪。这一幕像一把沉默的钥匙,“咔哒”一声,轻轻旋开了我记忆的闸门——我又回到了童年那个黄土坡上的小村庄。 那时,村里也有这样一位爷爷。农闲时候,他总背着竹篾编的背篼,沿着村道慢慢走,看见驴粪、骡粪,便俯身拾起。如今想来,这样的画面在很多年轻人眼中,恐怕已陌生如传说。在那个年月,村里几乎家家都守着几亩田地,十有八九都养着头驴或骡子,犁地、驮货都靠它们。牲口走过,路上常留


技术架构系列 - 详解Kafka
Prince-Peng2026/1/29

1. Kafka 知识脑图 2. Kafka 整体架构         首先,我们通过一张总览图来建立对Kafka生态系统的整体认知。这张图描绘了数据从生产到消费的完整路径,以及各核心组件之间的协作关系: 架构图解读: 数据流向:生产者(Producer)将消息推送(Push) 到Broker集群;消费者(Consumer)以拉取(Pull) 方式从Broker订阅消息。这种设计让消费者能根据自身处理能力控制速率,实现天然背压。核心角色: Broker:Kafka服务节点,


万字长文!搞懂强化学习的基础知识!
aicoting2026/1/20

推荐直接网站在线阅读:aicoting.cn 强化学习是什么? 强化学习(Reinforcement Learning, RL)是一类通过与环境交互来学习最优决策策略的机器学习方法。与监督学习不同,强化学习没有直接提供的“正确答案”,而是通过奖励信号(reward)来评估行为的好坏。智能体(agent)在环境(environment)中执行动作(action),根据环境反馈获得奖励,并观察状态(state)变化。 强化学习的目标是学习一个策略,使得智能体在长期交互中获得累计奖励最大化。典型方法包


从零构建 Vue 弹窗组件
yyt_2026/1/12

整体学习路线:简易弹窗 → 完善基础功能 → 组件内部状态管理 → 父→子传值 → 子→父传值 → 跨组件传值(最终目标) 步骤 1:搭建最基础的弹窗(静态结构,无交互) 目标:实现一个固定显示在页面中的弹窗,包含标题、内容、关闭按钮,掌握 Vue 组件的基本结构。 组件文件:BasicPopup.vue <template> <!-- 弹窗外层容器(遮罩层) --> <div class="popup-mask"> <!-- 弹窗主体 --> <div class="


深入UDP与sk_buff:掌握Linux网络协议栈的核心机制
咸鱼_要_翻身2026/1/3

目录 一、UDP 在网络协议栈中的位置 二、UDP 报文格式(RFC 768) 字段详解 三、UDP 如何解析报文?——定长头部分离机制 1、理解UDP报头 说明 注意事项 2、UDP数据封装流程:(自上而下) 3、UDP数据分用流程:(自下往上) 四、UDP 如何将数据交付给正确的应用进程?——端口分用(Demultiplexing) 工作流程 服务端 vs 客户端 五、UDP 的核心特性 1、无连接(Connectionless) 2、不可靠(Unrelia


iOS开发必备的HTTP网络基础概览
sweet丶2025/12/25

一、从一次HTTP请求说起 以下是一个大体过程,不包含DNS缓存等等细节: sequenceDiagram participant C as 客户端(iOS App) participant D as DNS服务器 participant S as 目标服务器 participant T as TLS/SSL层 Note over C,S: 1. DNS解析阶段 C->>D: 查询域名对应IP D-->>C: 返回IP地址


🚀你以为你在写 React?其实你在“搭一套前端操作系统”
白兰地空瓶2025/12/17

——从 Vite + React 架构出发,对照 Vue,彻底看懂现代前端工程化 👉 “现代前端不是写页面,而是在设计一套「运行在浏览器里的应用架构」。” 一、先说结论:React / Vue 早就不只是“框架”了 很多人学 React / Vue 的路径是这样的: JSX / template → 组件 → 状态 → 路由 → API 请求 ✋ 到此为止 但面试官想听的不是这个。 他们更关心的是: 你知不知道项目是怎么被“跑起来”的 dev / test / production


别让页面 “鬼畜跳”!Google 钦点的 3 个性能指标,治好了我 80% 的用户投诉
PineappleCoder2025/12/9

💥告别卡顿!前端性能优化第一课:Google钦点的三大核心指标,你真的懂吗? 欢迎来到前端性能优化专栏的第一课!在这个“用户体验至上”的时代,一个卡顿、缓慢、乱跳的网站,就像一辆抛锚在高速公路上的跑车,再酷炫也只会让人抓狂。别担心,Google已经为你准备好了一份“体检报告”——核心Web指标(Core Web Vitals) 。 今天,我们就来揭开这份报告的神秘面纱,用最通俗易懂的方式,让你彻底搞懂这三大指标,迈出性能优化的第一步! ✨ LCP(Largest Contentful Pa

首页编辑器站点地图

本站内容在 CC BY-SA 4.0 协议下发布

Copyright © 2026 XYZ博客