c# 第九章 record

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) recordrecord 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 structEquals(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

相关推荐
早期的虫儿有鸟吃8 小时前
vue2--Vuex 模块化
开发语言·前端·javascript
浪客川8 小时前
Android的SystemUI的启动流程简析
android·开发语言
-银雾鸢尾-8 小时前
C#中的抽象类与抽象方法
开发语言·c#
萧瑟余晖9 小时前
JDK 20 新特性详解
java·开发语言
benchmark_cc9 小时前
如何用 Python 进行多周期 K 线合成与时区对齐?基于 QuantDash 与 Pandas 的量化数据清洗实战(附 GitHub 源码)
开发语言·python·github·盯盘·pandas·quantdash·量化数据
2301_794461579 小时前
Activiti/BPMN 2.0 的 4 种网关
java·服务器·开发语言
147API9 小时前
Claude Tag 进入 Slack 后,团队智能体需要哪些任务与审计字段
java·开发语言·数据库
熬夜苦读学习9 小时前
QT_信号和槽
开发语言·qt
代码雕刻家9 小时前
编程语法细节
java·c语言·开发语言