C# 9 说"record 只能是 class"——C# 10 说"struct 也可以"
// 想做一个轻量 Point?
public record Point(double X, double Y);
// → record class:堆分配,每次 new 都 GC 跟踪
// 想用 struct 的性能?只能手写:
public struct Point : IEquatable<Point>
{
public double X { get; }
public double Y { get; }
public Point(double x, double y) { X=x; Y=y; }
public bool Equals(Point o) => X==o.X && Y==o.Y;
public override bool Equals(object o) => o is Point p && Equals(p);
public override int GetHashCode() => HashCode.Combine(X,Y);
public override string ToString() => $"Point {{ X = {X}, Y = {Y} }}";
public void Deconstruct(out double x, out double y) { x=X; y=Y; }
public static bool operator ==(Point a, Point b) => a.Equals(b);
// ... 还要 != operator、Clone 方法 ...
}
public readonly record struct Point(double X, double Y);
var p1 = new Point(3, 4);
var p2 = new Point(3, 4);
Console.WriteLine(p1 == p2); // True——值相等
// 栈上分配,无 GC 压力,自动生成全部方法
record class vs record structrecord class 引用类型 | record struct 值类型 | |
|---|---|---|
| 分配位置 | 堆 Heap | 栈(优先)Stack |
| GC 压力 | 有——每次 new 产生堆分配 | 无——除非装箱 |
| 可为 null | ✅ 引用类型天然可 null | ❌ 值类型不可 null(需 Point?) |
| 值相等 | ✅ Equals 基于所有属性 | ✅ Equals 基于所有属性(且不走反射) |
| with 表达式 | ✅ shallow clone(调用 <Clone>$) | ✅ 值拷贝(直接用构造函数) |
| ToString() | ✅ 自动生成 | ✅ 自动生成 |
| Deconstruct | ✅ 自动生成 | ✅ 自动生成 |
| 继承 | ✅ record class 之间可继承 | ❌ struct 不支持继承 |
<Clone>$ | ✅ 编译器生成 | ❌ 不需要——值类型直接拷贝 |
EqualityContract | ✅ 用于继承判别 | ❌ 不需要——无继承 |
readonly record struct vs record struct// ① readonly record struct —— 完全不可变(推荐默认)
public readonly record struct Point(double X, double Y);
// 所有属性自动 { get; init; },编译器强制不可变
var p = new Point(1, 2);
// p.X = 3; // ❌ 编译错误——readonly
// ② record struct(不加 readonly)——可变值类型 record
public record struct Counter(int Value);
// 属性 { get; set; },可修改
var c = new Counter(0);
c.Value++; // ✅ 可以——不是 readonly
readonly record struct。可变 struct 有反直觉的防御性拷贝Defensive Copy陷阱。不加 readonly 只在极少场景:你需要一个高频修改的小数据载体(计数器、累加器),且完全清楚可变 struct 的后果。
record struct 不默认 readonly?你会发现一个不对称:record class 默认不可变,record struct 默认可变。这是刻意设计,三个根因:
// struct:赋值 = 拷贝,修改副本不影响原值
var p1 = new Point(1, 2);
var p2 = p1; // 完整拷贝——两个独立对象
p2.X = 5; // p1.X 还是 1——安全 ✅
// class:赋值 = 拷贝引用,修改影响所有持有者
var r1 = new Person("Alice", 30);
var r2 = r1; // 拷贝引用——指向同一个对象
r2.Name = "Bob"; // r1.Name 也变成 "Bob"——危险 ❌
"拷贝即隔离"意味着可变 struct 的破坏力远小于可变 class。对 class,不可变是安全必需品;对 struct,不可变是锦上添花。
readonly 有历史传承| 版本 | 特性 | 设计逻辑 |
|---|---|---|
| C# 7.2 | readonly struct | 编译器可跳过防御性拷贝——性能优化 |
| C# 8.0 | readonly 实例方法 | 声明"此方法不修改状态" |
| C# 10 | readonly record struct | 延续:"struct 默认可变,按需锁定" |
// 热循环中的累加器——可变 struct 是刻意为之
public record struct Stats(int Count, double Sum);
var stats = new Stats(0, 0);
foreach (var item in items)
{
stats.Count++; // 原位修改——如果 readonly 就要每次 new 一个新对象
stats.Sum += item.Value;
}
record 关键字尊重了这一哲学。"
with 表达式——浅拷贝的真相with 做的是浅拷贝。但 struct 有个关键特性:如果只含值类型字段,效果上就是深拷贝:
// 场景 1:纯值类型字段 → 效果 = 深拷贝 ✅
public readonly record struct Point(double X, double Y);
var p1 = new Point(3, 4);
var p2 = p1 with { X = 5 }; // 逐字段拷贝——p1 和 p2 完全独立
// 场景 2:含引用类型字段 → 浅拷贝陷阱 ⚠
public readonly record struct Person(string Name, List<string> Tags);
var p1 = new Person("Alice", new List<string> { "admin" });
var p2 = p1 with { Name = "Bob" };
p2.Tags.Add("staff");
Console.WriteLine(p1.Tags.Count); // 2——p1.Tags 和 p2.Tags 指向同一个 List!
<Clone>$: p1 with { X = 5 } 等价于 new Point(X: 5, Y: p1.Y)——直接调用构造函数。值类型赋值就是天然的 clone,不需要额外机制。
// 你写:
public readonly record struct Point(double X, double Y);
// 编译器生成(简化):
// - init-only 属性 X, Y(因为 readonly)
// - 主构造函数,赋值给属性
// - Equals(Point other) —— 逐字段比较,不经反射
// - Equals(object) 装箱重载
// - == 和 != 运算符
// - GetHashCode() —— HashCode.Combine 模式
// - ToString() —— "Point { X = 3, Y = 4 }"
// - Deconstruct(out double X, out double Y)
// - 没有 <Clone>$ —— struct 直接拷贝
// - 没有 EqualityContract —— struct 不支持继承
<Clone>$——值类型赋值就是拷贝;② 没有 EqualityContract——struct 不能继承;③ Equals 不走反射,纯字段比较——更快。
| 选择 | 场景 |
|---|---|
readonly record struct | 小且不可变(≤ 16B)、高频创建销毁、无 GC 要求——Point、Color、UserId |
record class | 需要继承、可能为 null、字段多(> 16B)、生命周期长 |
record struct(非 readonly) | 需要可变 + 值语义——极少用,几乎总是选 readonly |
// ✅ 完美场景
public readonly record struct GeoPoint(double Lat, double Lng);
public readonly record struct Color(byte R, byte G, byte B);
// ✅ 高频创建——无 GC 压力
var points = Enumerable.Range(0, 10000)
.Select(i => new Point(i, i * 2)) // 零堆分配
.ToList();
// ❌ 不适合:需要继承(record class)、大对象频繁拷贝(struct 全量拷贝反而慢)
record struct 和 record class 的关键区别?record struct 不默认 readonly?readonly record struct——看省了多少行。