深入理解 C# 中的 Record 类型

深入理解 C# 中的 Record 类型_c# record-CSDN博客

深入理解 C# 中的 Record 类型

总目录


前言

在 C# 9.0 中引入了 record 关键字,用于定义记录类型(Record Type),这是一种新的引用类型,旨在简化不可变数据类型的定义与操作。记录类型具有许多独特的特性,如值相等性、简洁的语法和内置的不可变性支持,使其成为处理数据传输对象(DTO)、配置类以及其他需要不可变性的场景的理想选择。本文将详细介绍 C# 中记录类型的定义、特性和最佳实践。


一、什么是 Record 记录类型

1. 定义

记录类型 是一种特殊的引用类型,主要用于表示不可变的数据结构。与普通类不同,记录类型提供了默认的值相等性比较,并且支持简化的构造函数和解构模式。

2. 基础示例

假设我们有一个简单的 Person 记录类型:

public record Person(string Name, int Age);
csharp 运行
  • 1

在这个例子中,Person 是一个记录类型,它包含两个属性:NameAge。记录类型会自动生成构造函数、解构方法和其他必要的成员。

二、为什么需要 Record?

1. 传统类的局限

传统类的局限性在数据建模中愈发明显:

  • 繁琐的样板代码:实现不可变类需手动定义只读属性、重写相等性方法
  • 线程安全隐患:可变状态在多线程环境中易引发竞态条件
  • 值语义缺失:类默认基于引用比较,数据相等性判断复杂

record应运而生,完美解决这些问题。它融合了引用类型的继承特性值类型的相等语义,成为不可变数据模型的理想载体。

2. Record 的优势

  • 简化代码record 提供了一种简洁的方式来定义数据传输对象,减少了样板代码。
  • 提高安全性record 的不可变性确保了数据在传输过程中的安全性。
  • 高可读性record 的明确字段名称和简洁语法使得代码更加易于理解和维护。

三、Record 详解

1. 核心特性

1)简洁的语法

记录类型提供了一种简洁的语法来定义不可变的数据结构。你只需要指定属性名称和类型,编译器会自动生成相应的构造函数、解构方法和属性访问器。

  • 编译器生成 ToString()(输出结构化的属性值)

    public record Person(string Name, int Age);
    
    class Program
    {
        static void Main()
        {
            var person = new Person("Alice", 30);
            Console.WriteLine(person);
            // 输出:Person { Name = Alice, Age = 30 }
        }
    }
    
    csharp 运行
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
  • 支持解构操作(可通过 Deconstruct 方法提取属性)

    public record Person(string Name, int Age);
    
    class Program
    {
        static void Main()
        {
            var person = new Person("Alice", 30);
    
            //(var name, var age) = person;
            var (name, age) = person; 	// 解构为元组
            Console.WriteLine(name);    // 输出:Alice
        }
    }
    
    csharp 运行
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
  • 自动实现init属性
    在这里插入图片描述

2)值相等性

record 类型支持值相等性,即两个 record 对象的内容相等时,它们被认为是相等的。

public record Person(string Name, int Age);

class Program
{
    static void Main()
    {
        var person1 = new Person("Alice", 30);
        var person2 = new Person("Alice", 30);

        Console.WriteLine(person1 == person2); // 输出: True
    }
}
csharp 运行
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

Record 的 Equals== 运算符通过属性值比较对象是否相等,而非引用地址。
这点与Class不同,Class 需要重写 Equals 和重载 == 运算符 才能实现 自定义对象属性值的比较,而Record 自动实现EqualsGetHashCode 以及重载 == 运算符。

public record Person(string Name, int Age);
class Program
{
    static void Main()
    {
        var p1 = new Person("Alice", 30);
        var p2 = new Person("Alice", 30);
        var p3 = p2;
        var p4 = new Person("Jack",30);
        Console.WriteLine(p1 == p2); // 输出 True(值相等)
        Console.WriteLine(p1 == p3); // 输出 True(值相等)
        Console.WriteLine(p1 == p4); // 输出 False(值不相等)

        Console.WriteLine(p1.Equals(p2));// 输出 True(值相等)
        Console.WriteLine(p1.Equals(p3));// 输出 True(值相等)
        Console.WriteLine(p1.Equals(p4));// 输出 False(值不相等)

        Console.WriteLine(object.ReferenceEquals(p1,p2));// 输出 False(引用不相等)
        Console.WriteLine(object.ReferenceEquals(p1,p3));// 输出 False(引用不相等)
        Console.WriteLine(object.ReferenceEquals(p1,p4));// 输出 False(引用不相等)

    }
}
csharp 运行
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

上例展示了 record 类型 分别使用 Equals 方法和 == 运算符以及ReferenceEquals方法进行比较的区别。

3)不可变性(默认行为)

Record 类型默认属性为只读(get; init;),创建后不可修改,确保线程安全。

public record Person(string Name, int Age);

class Program
{
     static void Main()
     {
         var person = new Person("Alice", 30);
         //person.Name = "";//由于不可变性,当给其赋值时,编译错误
         Console.WriteLine(person.Name);
     }
}
csharp 运行
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

4)with 表达式

基于现有实例创建新对象(类似函数式编程的"复制-修改"), Record 默认不可变,属性只能在初始化时赋值。若需修改,需通过 with 表达式创建新副本:

public record Person(string Name, int Age);

class Program
{
    static void Main()
    {
        var person1 = new Person("Alice", 30);
        var person2 = person1 with { Age = 31 }; // 生成新对象,保留其他属性。

        Console.WriteLine(person1); // 输出: Person { Name = Alice, Age = 30 }
        Console.WriteLine(person2); // 输出: Person { Name = Alice, Age = 31 }
    }
}
csharp 运行
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

5)模式匹配支持

结合 switch 实现基于属性的条件分支。

public record Person(string Name, int Age);

class Program
{
    static void Main()
    {
        var person = new Person("Alice", 30);

        switch (person)
        {
            case Person { Age: >= 18 } p: Console.WriteLine($"{p.Name} is Adult"); break;
            case Person { Age: < 18 and > 0 } p: Console.WriteLine($"{p.Name} is Minor"); break;
        }
    }
}
csharp 运行
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

6)继承与派生

记录类型支持继承和派生,但有一些限制。子类必须也是记录类型,并且基类的参数必须在子类的构造函数中传递。

public record Person(string Name, int Age);
public record Employee(string Name, int Age, string Department) : Person(Name, Age);

class Program
{
    static void Main()
    {
        var employee = new Employee("Alice", 30, "Engineering");
        Console.WriteLine(employee); // 输出: Employee { Name = Alice, Age = 30, Department = Engineering }
    }
}
csharp 运行
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

2. 语法变体

1)record class(默认)

  • 引用类型record class 或简写 record
  • 引用类型,但基于值语义比较
 public record Student(string Id, string Major);
csharp 运行
  • 1

2)record struct(C# 10+)

  • 结构体类型record structreadonly record struct
  • 值类型,适用于轻量级数据
public record struct Point(int X, int Y);
csharp 运行
  • 1
public readonly record struct Point(int X, int Y); // 不可变值类型。
csharp 运行
  • 1

值类型记录与引用类型记录的主要区别在于内存分配和复制行为。值类型记录在赋值时会进行深拷贝,而引用类型记录则共享引用。

3)属性自定义

可通过 init 访问器实现部分可变性(初始化后不可修改)。

public record Person
{
 public string Name { get; init; }
 public int Age { get; init; }
}

class Program
{
 static void Main()
 {
     var person = new Person() { Name = "Jack", Age = 15 };
     person.Name = "Nan"; //初始化赋值后,更改会编译错误
 }
}
csharp 运行
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

3. 高级用法

1)自定义 Record

可扩展 Record 以添加方法或重写默认行为:

public record Person(string FirstName, string LastName)
{
    public string FullName => $"{FirstName}{LastName}";
    public override string ToString() => $"Person: {FullName}";
}
csharp 运行
  • 1
  • 2
  • 3
  • 4
  • 5
public record Person
{
    public required string Name { get; init; }

    public string Greet() => $"Hello, {Name}!";
}
csharp 运行
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

2)可变 Record(不推荐)

通过 { get; set; } 定义可变属性,但违背 Record 设计初衷。

public record Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}
csharp 运行
  • 1
  • 2
  • 3
  • 4
  • 5

3)特性标注


using System.Text.Json;
using System.Text.Json.Serialization;

public record Person(
    [property: JsonPropertyName("name")] string Name,
    [property: JsonPropertyName("age")] int Age
);


class Program
{
    static void Main()
    {
        var person = new Person("Jack",18);
        var json = JsonSerializer.Serialize(person);
        Console.WriteLine(json);//输出:{"name":"Jack","age":18}
    }
}
csharp 运行
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

4. 注意事项

  • 若需要可变性,可显式声明 set 访问器,但会破坏值相等语义
  • 避免在 record 中封装复杂业务逻辑,保持其数据容器的定位
  • 深度嵌套对象时,with 表达式只执行浅拷贝

四、适用场景

1. DTO(数据传输对象)

在分布式系统中,记录类型可以用于定义请求和响应的数据结构,简化 API 请求/响应模型的创建。

public record CustomerDto(int Id, string Name, string Email);
csharp 运行
  • 1

2. 配置对象

在应用程序配置中,记录类型可以用来存储和传递配置信息(确保配置加载后不可篡改)。如数据库连接字符串。

public record AppConfig(string ConnectionString, int MaxRetries);
csharp 运行
  • 1

3. 不可变数据模型

记录类型非常适合用于需要保持数据一致性和不可变性的场景。
领域模型中的值对象(Value Object),确保数据不可变,避免副作用

4. 模式匹配

结合 switch 表达式实现高效数据匹配

var result = person switch
{
    { Age: >= 18 } => "Adult",
    _ => "Minor"
};

public string GetPersonInfo(Person p) => p switch
{
    { Age: < 18 } => "Minor",
    _ => "Adult"
};
csharp 运行
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

五、Record 相关比较

1. Record 对比概览

特性RecordClass
不可变性 默认不可变 默认可变
相等性比较 基于属性值 基于引用地址
语法简洁性 一行代码定义完整类型 需手动编写属性和方法
复制与修改 支持 with 表达式 需手动实现克隆逻辑
继承 支持继承其他 Record 支持继承类或接口
ToString() 自动生成属性摘要 返回类型名称
操作record classstruct
内存分配 堆分配 栈分配
相等性比较 O(n)属性比较 内存逐字节比较
大对象传递 引用传递 值拷贝

优化建议:超过16字节的数据优先使用record class

特性引用类型 record结构体 record struct
值类型/引用类型 引用类型 值类型
属性可变性 默认不可变(init 可选) 默认不可变(init 可选)
继承支持 支持继承其他 record 不支持继承
内存占用 较高(堆分配) 较低(栈分配)

2. 示例对比

1)简洁语法

Record 可通过一行代码定义包含多个属性的不可变类型。例如,定义一个表示“人”的 Record:

public record Person(string FirstName, string LastName);
csharp 运行
  • 1

编译器会自动生成构造函数、只读属性、基于值的相等性比较方法等。

2)传统类与 Record 的对比

传统类需要显式定义属性、构造函数和相等性方法,而 Record 将这些代码自动生成,减少样板代码量。例如:

// 传统类定义
public class Person
{
    public string FirstName { get; }
    public string LastName { get; }
    public Person(string firstName, string lastName) { ... }
    // 需手动实现 Equals、GetHashCode 等
}
csharp 运行
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

六、最佳实践

1. 保持简洁

记录类型应该保持简洁,专注于表示不可变的数据。避免在记录类型中添加复杂的业务逻辑或行为。

2. 使用场景

  • record 适用于需要传输数据但不需要复杂业务逻辑的场景,例如数据传输对象(DTO)。
  • 虽然 record 提供了便利,但在需要频繁修改对象状态的场景中,应避免使用。

3. 使用 init 访问器

如果你需要在对象初始化后允许某些属性被设置一次,可以使用 init 访问器。这样可以在保持不可变性的同时,允许有限的修改。

public record Person(string Name, int Age)
{
    public string Address { get; init; }
}

class Program
{
    static void Main()
    {
        var person = new Person("Alice", 30) { Address = "123 Main St" };
    }
}
csharp 运行
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

4. 结合 record 和普通类

在需要实现接口或复杂逻辑时,可以将 record 与普通类结合使用。

5. 处理复杂映射

当你的记录类型包含嵌套对象时,确保正确处理这些嵌套对象的映射。你可以使用 AutoMapper 或其他映射库来简化这一过程。

public record Address(string Street, string City, string State);
public record Person(string Name, int Age, Address Address);

class Program
{
    static void Main()
    {
        var address = new Address("123 Main St", "New York", "NY");
        var person = new Person("Alice", 30, address);

        Console.WriteLine(person); // 输出: Person { Name = Alice, Age = 30, Address = Address { Street = 123 Main St, City = New York, State = NY } }
    }
}
csharp 运行
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

6. 版本控制

如果你的应用程序需要支持多个版本的 API,考虑为每个版本创建不同的记录类型。这样可以避免破坏现有客户端的兼容性。

public record PersonV1(string Name);
public record PersonV2(string Name, int Age);
csharp 运行
  • 1
  • 2

结语

回到目录页:C#/.NET 知识汇总
希望以上内容可以帮助到大家,如文中有不对之处,还请批评指正。


参考资料:
Microsoft Docs: Records in C#
Best Practices for Using Records in C#
AutoMapper Documentation

 
  • 打赏
  • 打赏 打赏 举报
    举报
C# Record类型深度解析:构建不可变数据结构与实现模式匹配的艺术
04-25 1181
C# 9.0引入了一种新型的引用类型——
C# 技术使用笔记:记录(Record)使用方法入门
03-25 830
C#中,record是一种特殊的引用类型,它主要用于表示不可变的数据结构。自C# 9.0引入以来,record便以其独特的特性在程序设计中占据了一席之地。定义方式record的定义方式与类类似,但使用关键字record替代class。例如这样就定义了一个名为Person的record,包含两个属性Name和Age。不可变性:这是record的核心特点之一。一旦创建了一个record实例,其属性值就不能被修改。例如,对于上述Person记录,如果创建了一个实例那么和person.Age。
C# record的理解_c# record 继承
9-3
record声明的实体在编译时会根据构造参数生成一个默认的构造函数,默认的构造函数不能被覆盖,如果有自定义的构造函数,那么需要使用this关键字来初始化这个默认的构造函数。 record声明的实体中可以自定义属性,自定义属性名可以和构造参数名重名,也就是说自定义属性可以覆盖构造参数生成的属性,此时对应的构造参数将不起任何...
C# 中记录(Record)详解
9-8
C#9.0中,我们使用record关键字声明一个记录类型,它只能是引用类型: public record Animal; 从C#10开始,我们不仅有引用类型记录,还有结构体记录: //使用record class声明为引用类型记录,class关键字是可选的,当缺省时等价于C#9.0中的record用法 publicrecordAnimal; //等价于 publicrecordclassAnimal; //使用record ...
C# 下记录(Record)详解
C# record关键字用于定义记录类型,这是一种不可变的数据结构,用于表示具有明确字段名称和类型的数据集。本文将详细介绍C#record类型的使用和特点,以及如何通过记录记录器(如Console.WriteLine)与用户输入进行互动。
C# 中的 record类型详解
最新发布
06-30 341
C# 中的 record类型详解
C#中的记录类型:Records
8-12
传统使用类或结构体实现时,需要手动重写Equals()、GetHashCode()和ToString()方法,并处理不可变性。C# 9.0引入的记录类型(Records) 完美解决了这些问题,提供了一种简洁、安全的数据建模方式。 一、基础语法与创建方式 1. 位置记录(最简洁语法) publicrecordPerson(stringFirstName,...
C#中的record关键字
9-6
C#中的record关键字 C# 中的 record 类型C# 9.0 中的一个新特性,用于创建不可变的数据类型。它可以用于定义简单的数据传输对象(DTO)、消息传递对象(MDO)等。 以下是一个示例的 C# record 类型: public record Person { public string Name { get;init; }...
C# 中的记录(record类型和类(class)类型对比总结
记录(record类型相比类(class)类型,有很多不同的地方,但它本质上也是一个类(class)类型,所以在使用时尤其要注意其边界,在正确的场景中使用它,才能有化腐朽为神奇的效果
C# record
10-27 1090
1.概要C# record 是一种引用类型,是C# 9.0引入的新特性。它是一种轻量级的、不可变的数据类型,具有只读属性,因此在创建后无法更改,这使得它线程安全。与类不同,record 类型是基于值相等而不是唯一标识符的,这意味着两个 record 实例只要它们的属性相等,就被视为相等。Records 在数据传输、模式匹配和不可变性方面非常有用。它们提供了更简洁的语法,用于创建不可变的数据对象,避...
深入理解C#中的Net5Lambdas特性
 
C# 9.0在.NET 5中引入了更多新特性,比如record类型、模式匹配增强、目标类型new表达式等。 总结而言,Net5Lambdas项目或示例可能会围绕上述知识点展开,提供一些实际使用.NET 5和Lambda表达式的代码示例、教程或...
C# record 深入解析
03-08
好的,我现在需要深入解析C#中的record类型。首先,我得回想一下自己之前学过的关于record知识,可能还需要查阅一些资料来确保准确性。 首先,record是在C# 9引入的,对吧?它的主要用途是什么?我记得它是用来...
深入理解C# 第3版》- 进阶程序员的学习宝典
 
3. .NET框架与CLR(公共语言运行时):C#作为.NET平台上的主要编程语言,了解.NET框架以及CLR的工作原理是深入理解C#不可或缺的一部分。NET框架提供的类库和API能够支持各种开发任务,包括网络编程、文件操作、XML...
C#9新特性:record类型与模式匹配的实际应用
 
C# 9的新特性中,"record类型"是一项引人关注的改进。Record类型,虽然有些人将其翻译为“记录类型”,但老周个人更倾向于使用原词,因为这个词简洁直观。Record类型本质上是一种引用类型,与类(C#中的Class)非常...
基于C#的UDS协议车载BootLoader上位机开发:多品牌CAN卡适配与S-record解析
03-28
其他说明:文中提到的多个代码片段和设计思路可以帮助开发者更好地理解和实现相关功能,同时也提供了一些常见问题的解决方案,如CAN卡API对ISO15765的支持差异、S-record文件解析中的地址连续性问题等。
C# 9.0:Records
转自:翁智华cnblogs.com/wzh2010/p/13950647.html概述在C# 9.0下record是一个关键字,微软官方目前暂时将它翻译为记录类型。传统面向对象的编程的核心思想是一个对象有着唯一标识,封装着随时可变的状态。C#也是一直这样设计和工作的。但是一些时候,你就非常需要刚好对立的方式。原来那种默认的方式往往会成为阻力,使得事情变得费时费力。如果你发现你需要整个对象都是不可变...
F# ≥ C# (Record)
继续上一篇叙述Tuple和Swap的文章,现在我们继续探索:相比C#编程,如何轻松的使用F#来减少你的编码工作。 直到最近,我才开始注意到在F#中有一个叫做记录(Record)的类型,之前,我以为它类似于C#中的结构或者类,但是,实际并非如此。 首先介绍下什么是记录:记录表示命名值的简单聚合,并可以选择包含成员。 我们先来看如下代码: type myPointRecord = { X ...
C#转义字符
转义序列 字符 ’ 单引号 " 双引号 \ 反斜杠 \0 空 \a 警告 \b 退格 \f 换页 \n 换行 \r 回车 \t 水平制表符 \v 垂直制表符
C# 梳理二 新增特性 record
C# 中的 Record 类型是一种轻量级的、用于表示不可变数据的特殊类型。它结合了面向对象编程的优点,提供了简洁而强大的语法,使得创建和操作不可变数据更加容易。
C#9.0记录类型
06-29 461
引用类型 提供合成方法来提供值语义,从而实现相等性。默认情况下,记录是不可变的。 记录类型可以轻松创建不可变的引用类型。以前.net类型主要分为引用类型(包括类和匿名类型)和值类型(包括结构和元组)。虽然建议使用不可变的值类型,但可变的值类型通常不会引入错误。值类型变量可保存值,因此在将值类型传递给方法时,会对原始数据的副本进行更改。不可变的引用类型。记录为不可变的引用类型提供类型声明,该引用类型使用值语义实现相等性。如果用于实现相等性的合成方法的属性和哈希代码的属性都相等,则认为两条记录相等。
C#/.NET】record介绍
06-06 641
record是.NET 5中的一种新特性,可以看作是一种概念上不可变的类。records可以帮助我们在C#中更容易地处理数据,同时提供了重要的功能,如对象相等性、hashcode和解构。与类不同,records具有值语义。也就是说,当比较两个records的实例时,比较的是这些实例的属性而非引用。这意味着,如果两个records的属性值相同,它们就是相等的。record也可以简化需要类似于Dto的数据结构容器的定义。
216原创
3922
点赞
6230
收藏
2064
粉丝
关注
上一篇:
深入理解 C# 中的主构造函数
下一篇:
深入理解 C# 中的 DTO(数据传输对象)

最新评论

posted on 2025-09-12 17:44  漫思  阅读(9)  评论(0)    收藏  举报

导航