引用类型(class)的实例存储在托管堆上,变量保存的是对象的引用。根据对象创建后其状态是否允许被修改,可以将引用类型分为可变(Mutable)和不可变(Immutable)两类。
1. 可变引用类型
定义:对象创建后,其内部状态(字段、属性)可以被修改。
特点:
-
可以通过公开的 setter 或方法更改属性值。
-
同一对象在不同时间点可能呈现不同状态。
-
多线程环境下需要同步机制保证线程安全。
-
容易产生副作用,因为多个引用可能指向同一对象,一处修改会影响所有引用。
示例:StringBuilder、List<T>、自定义的带有 setter 的类。
2. 不可变引用类型
定义:对象一旦创建,其内部状态就完全确定,且在整个生命周期内不会发生任何变化。
特点:
-
通常所有字段都是只读的(readonly),且只能在构造函数中初始化。
-
不提供修改内部状态的方法(无 setter,无改变字段的方法)。
-
“修改”操作会返回一个新的实例,原实例保持不变。
-
天然线程安全,可自由共享而无需同步。
-
便于缓存、哈希键等场景(因为状态固定,哈希值不变)。
示例:string(字符串连接等操作返回新字符串)、System.Uri、System.DateTime(虽然它是值类型,但也是不可变的)、自定义的不可变类。
3. 代码示例
3.1 不可变引用类型示例
使用 string
string s1 = "Hello";
string s2 = s1; // s2 和 s1 引用同一个字符串对象
Console.WriteLine($"s1: {s1}, s2: {s2}"); // 输出:Hello, Hello
// 尝试“修改”s1 —— 实际上是创建了一个新字符串对象
s1 = s1 + " World";
Console.WriteLine($"s1: {s1}, s2: {s2}");
// 输出:s1: Hello World, s2: Hello (s2 仍然指向原字符串)
自定义不可变类 ImmutablePerson
public class ImmutablePerson
{
public string Name { get; }
public int Age { get; }
public ImmutablePerson(string name, int age)
{
Name = name;
Age = age;
}
// “修改”年龄:返回一个新对象
public ImmutablePerson WithAge(int newAge)
{
return new ImmutablePerson(this.Name, newAge);
}
public override string ToString() => $"{Name} ({Age})";
}
// 使用
var p1 = new ImmutablePerson("Alice", 30);
var p2 = p1.WithAge(31); // p1 保持不变,p2 是新对象
Console.WriteLine(p1); // Alice (30)
Console.WriteLine(p2); // Alice (31)
3.2 可变引用类型示例
使用 StringBuilder
StringBuilder sb1 = new StringBuilder("Hello");
StringBuilder sb2 = sb1; // sb2 和 sb1 引用同一个对象
Console.WriteLine(sb1); // Hello
Console.WriteLine(sb2); // Hello
// 修改 sb1
sb1.Append(" World");
// sb2 也受到影响,因为它们指向同一对象
Console.WriteLine(sb1); // Hello World
Console.WriteLine(sb2); // Hello World
自定义可变类 MutablePerson
public class MutablePerson
{
public string Name { get; set; }
public int Age { get; set; }
public override string ToString() => $"{Name} ({Age})";
}
// 使用
var p1 = new MutablePerson { Name = "Bob", Age = 25 };
var p2 = p1; // p2 引用同一个对象
p1.Age = 26; // 修改 p1
Console.WriteLine(p1); // Bob (26)
Console.WriteLine(p2); // Bob (26) —— p2 也变了
4. 总结对比
| 状态修改 | 可以直接修改对象内容 | 修改操作返回新对象 |
| 线程安全 | 通常不安全,需同步 | 天然安全,可自由共享 |
| 内存/性能 | 修改原地进行,无需额外分配 | 每次修改可能分配新对象 |
| 适用场景 | 需要频繁修改状态的场景 | 需要共享、缓存、哈希键的场景 |
| 常见例子 | StringBuilder, List<T> | string, System.Uri |
网硕互联帮助中心





评论前必须登录!
注册