record
C# Record 类型详解
一、什么是 Record?
record 是 C# 9.0 引入的关键字,用于定义记录类型。它是一种以数据为中心的类型,编译器会自动生成大量模板代码(相等性比较、ToString、GetHashCode 等),让你用极少的代码就能定义出功能完整的数据类型。
类比:C# 的 record 类似于 Kotlin 的 data class 或 Scala 的 case class。
二、基本语法
位置参数语法(最简洁)
// 一行定义一个完整的类型!
public record Person(string FirstName, string LastName, int Age);
编译器自动生成:
✅ 构造函数
✅ 只读属性(init 访问器)
✅ 值相等性(Equals、==、!=)
✅ GetHashCode()
✅ ToString()(输出如 Person { FirstName = "张", LastName = "三", Age = 25 })
✅ 解构方法 Deconstruct
✅ with 表达式支持
传统语法
public record Person
{
public string FirstName { get; init; }
public string LastName { get; init; }
public int Age { get; init; }
}
使用
var p1 = new Person("张", "三", 25);
Console.WriteLine(p1); // Person { FirstName = 张, LastName = 三, Age = 25 }
// 解构
var (first, last, age) = p1;
三、核心特性
📌 值相等性(Value-based Equality)
普通 class 比较的是引用,record 比较的是值:
var p1 = new Person("张", "三", 25);
var p2 = new Person("张", "三", 25);
Console.WriteLine(p1 == p2); // True ✅(class 则为 False)
Console.WriteLine(p1.Equals(p2)); // True ✅
Console.WriteLine(ReferenceEquals(p1, p2)); // False(仍是不同对象)
📌 不可变性(Immutability)
位置参数生成的属性默认是 init 只读的:
var p = new Person("张", "三", 25);
p.FirstName = "李"; // ❌ 编译错误!init-only 属性不可修改
📌 with 表达式(非破坏性修改)
创建一个"副本"并修改部分属性,原对象不变:
var p1 = new Person("张", "三", 25);
var p2 = p1 with { Age = 26, LastName = "四" };
Console.WriteLine(p1); // Person { FirstName = 张, LastName = 三, Age = 25 } (不变)
Console.WriteLine(p2); // Person { FirstName = 张, LastName = 四, Age = 26 } (新对象)
📌 继承
record 支持继承:
public record Person(string FirstName, string LastName);
public record Student(string FirstName, string LastName, string School)
: Person(FirstName, LastName);
var s = new Student("张", "三", "清华大学");
Console.WriteLine(s); // Student { FirstName = 张, LastName = 三, School = 清华大学 }
📌 模式匹配
string Describe(Person p) => p switch
{
{ Age: < 18 } => "未成年",
{ Age: >= 18 and < 60 } => "成年人",
_ => "老年人"
};
四、record class vs record struct
特性 record / record class (C# 9) record struct (C# 10)
类型 引用类型(堆分配) 值类型(栈分配)
默认不可变 ✅ 属性为 init ❌ 属性默认可变(需加 readonly)
相等性 值相等 值相等
with 表达式 ✅ ✅
继承 ✅ 支持 ❌ 不支持
适用场景 较大的数据模型 小型、高性能数据结构
// record struct 示例
public readonly record struct Point(int X, int Y);
var p1 = new Point(1, 2);
var p2 = p1 with { X = 10 };
五、典型应用场景
// 1️⃣ DTO / API 响应模型
public record UserDto(int Id, string Name, string Email);
// 2️⃣ 配置项
public record DatabaseConfig(string Host, int Port, string DbName);
// 3️⃣ 函数返回值(多值返回)
public record Result(bool Success, T? Data, string? Error);
// 4️⃣ 事件 / 消息
public record OrderCreatedEvent(int OrderId, DateTime CreatedAt);
六、与普通 class 的对比
// ❌ 传统写法:需要大量模板代码
public class Person
{
public string FirstName { get; }
public string LastName { get; }
public Person(string firstName, string lastName) { ... }
public override bool Equals(object obj) { ... }
public override int GetHashCode() { ... }
public override string ToString() { ... }
}
// ✅ record 写法:一行搞定
public record Person(string FirstName, string LastName);
七、注意事项
注意点 说明
不是完全不可变 可以定义可变属性,但会失去不可变性的优势
record struct 默认可变 需要加 readonly 修饰符才能不可变
不适合有复杂行为的类型 record 设计初衷是"数据载体",复杂逻辑仍用 class
序列化 大多数序列化库(System.Text.Json、Newtonsoft)已支持 record
总结:当你需要一个以数据为中心、不可变、值相等的类型时,优先使用 record。它能大幅减少样板代码,让代码更简洁、更安全。
record struct 和struct record的比较
C# record struct vs record class 全面对比
一、版本背景
| 类型 | 引入版本 | 关键字 |
|---|---|---|
record class |
C# 9.0 (.NET 5) | record 或 record class |
record struct |
C# 10.0 (.NET 6) | record struct |
record单独使用时,等价于record class。
二、核心差异总览
| 特性 | record class |
record struct |
|---|---|---|
| 底层类型 | 引用类型(class) |
值类型(struct) |
| 内存分配 | 堆(Heap) | 栈(Stack)/ 内联 |
| GC 压力 | 有 | 无 |
| 默认可变性 | ❌ 不可变(init) |
✅ 可变 (set)⚠️ |
| 相等性 | 值相等 | 值相等 |
with 表达式 |
✅ | ✅ |
| 继承 | ✅ 支持 | ❌ 不支持 |
| 接口实现 | ✅ | ✅ |
| 可为 null | ✅ 可为 null |
❌ 不可为 null(需 ?) |
ToString() |
✅ 自动生成 | ✅ 自动生成 |
解构 Deconstruct |
✅ | ✅ |
| 默认值 | null |
default(T)(所有字段为 0) |
三、最关键的区别:默认可变性 ⚠️
这是最容易踩坑的地方:
record class → 默认不可变
csharp
public record Person(string Name, int Age);
var p = new Person("张三", 25);
p.Name = "李四"; // ❌ 编译错误!属性是 init-only
record struct → 默认可变
csharp
public record struct Point(int X, int Y);
var p = new Point(1, 2);
p.X = 100; // ✅ 完全合法!属性是 set 的
让 record struct 也变成不可变
csharp
// 方法一:加 readonly 修饰符
public readonly record struct Point(int X, int Y);
var p = new Point(1, 2);
p.X = 100; // ❌ 编译错误
// 方法二:手动用 init
public record struct Point(int X, int Y)
{
public int X { get; init; } = X;
public int Y { get; init; } = Y;
}
四、相等性对比
两者都是值相等,但实现细节不同:
csharp
// record class
public record Person(string Name, int Age);
var a = new Person("张三", 25);
var b = new Person("张三", 25);
Console.WriteLine(a == b); // True(值相等)
Console.WriteLine(ReferenceEquals(a, b)); // False(不同对象)
// record struct
public record struct Point(int X, int Y);
var p1 = new Point(1, 2);
var p2 = new Point(1, 2);
Console.WriteLine(p1 == p2); // True(值相等)
Console.WriteLine(p1.Equals(p2)); // True
注意 :
record struct的Equals(object)会触发装箱(boxing) ,在性能敏感场景应使用Equals(Point)或==。
五、with 表达式
两者都支持,但行为不同:
csharp
// record class:创建新对象(堆分配)
var p1 = new Person("张三", 25);
var p2 = p1 with { Age = 26 };
// p1 和 p2 是不同引用
// record struct:创建新副本(栈上复制)
var pt1 = new Point(1, 2);
var pt2 = pt1 with { X = 10 };
// pt1 和 pt2 是独立副本
六、继承
csharp
// ✅ record class 支持继承
public record Animal(string Name);
public record Dog(string Name, string Breed) : Animal(Name);
// ❌ record struct 不支持继承
public record struct Base(int X);
public record struct Derived(int X, int Y) : Base(X); // ❌ 编译错误!
七、Null 处理
csharp
// record class 可以为 null
Person? p = null; // ✅
// record struct 不能为 null(值类型)
Point p = null; // ❌ 编译错误
Point? p = null; // ✅ 使用 Nullable<Point>
八、ToString() 输出
csharp
public record Person(string Name, int Age);
public record struct Point(int X, int Y);
Console.WriteLine(new Person("张三", 25));
// 输出: Person { Name = 张三, Age = 25 }
Console.WriteLine(new Point(1, 2));
// 输出: Point { X = 1, Y = 2 }
两者格式一致,都很好。
九、性能对比
csharp
// 高频创建场景
// record struct:栈分配,零 GC,性能更优
for (int i = 0; i < 1_000_000; i++)
{
var p = new Point(i, i); // 栈上分配,无 GC
}
// record class:每次都在堆上分配,产生 GC 压力
for (int i = 0; i < 1_000_000; i++)
{
var p = new Person("张三", i); // 堆分配,有 GC
}
| 场景 | 推荐 |
|---|---|
| 高频创建的小型数据(坐标、颜色、向量) | record struct |
| 较大的数据模型、DTO | record class |
| 需要继承层次结构 | record class |
| 需要 null 语义 | record class |
十、完整代码示例
csharp
// ========== record class ==========
public record Person(string Name, int Age)
{
// 可以添加方法
public string Greet() => $"你好,我是{Name},今年{Age}岁";
}
// 继承
public record Student(string Name, int Age, string School)
: Person(Name, Age);
// 使用
var p = new Person("张三", 25);
var s = new Student("李四", 20, "清华大学");
var p2 = p with { Age = 26 };
Console.WriteLine(p); // Person { Name = 张三, Age = 25 }
Console.WriteLine(p == p2); // False(Age 不同)
Console.WriteLine(s.Greet()); // 你好,我是李四,今年20岁
// ========== record struct ==========
public readonly record struct Color(byte R, byte G, byte B)
{
public string ToHex() => $"#{R:X2}{G:X2}{B:X2}";
}
// 使用
var red = new Color(255, 0, 0);
var blue = red with { R = 0, B = 255 };
Console.WriteLine(red); // Color { R = 255, G = 0, B = 0 }
Console.WriteLine(red.ToHex()); // #FF0000
Console.WriteLine(red == blue); // False
十一、如何选择?
需要继承? ──是──→ record class
│
否
↓
数据很小(≤16字节)且高频创建? ──是──→ record struct
│
否
↓
需要 null 语义? ──是──→ record class
│
否
↓
默认选择 → record class(更安全、更通用)
经验法则 :大多数场景用
record class(即record);只有在性能敏感 + 数据很小 + 不需要继承 时,才用record struct。