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

相关推荐
呆呆敲代码的小Y1 分钟前
【游戏开发】C# 中的迭代器
java·开发语言·c#·迭代器
benchmark_cc3 分钟前
K 线数据断层会对回测造成什么影响?从一个缺失交易日看懂量化策略为什么会失真
大数据·开发语言·数据分析·量化·股票数据·quantdash·量化数据源
benchmark_cc8 分钟前
数据 API 稳定性为什么会影响量化策略?从数据获取到信号执行的完整分析
开发语言·python·数据分析·量化·股票数据·quantdash·量化数据源
2601_9621803322 分钟前
python——Django 框架
开发语言·python·django
长江后浪博客23 分钟前
Conda环境下测试Intel NPU:Python版本如何选择?
开发语言·python·conda·openvino·intel npu
Chester_199936 分钟前
CSP202206C.角色授权
开发语言·数据结构·c++·蓝桥杯
晴天的雨.99243 分钟前
类和对象下(内部类,匿名对象,对象拷贝时的编译器优化)
开发语言·c++·算法
其实防守也摸鱼1 小时前
智能体推荐:精选 AI Agent 工具与实战指南
运维·开发语言·人工智能·学习·web安全·自动化
always_TT1 小时前
【Python 字符串格式化:format() 方法】
android·开发语言·python
小义_1 小时前
JDK 深度解析
java·linux·开发语言·python·面试