前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >C# record

C# record

作者头像
JusterZhu
发布2023-10-24 13:57:05
1730
发布2023-10-24 13:57:05
举报
文章被收录于专栏:JusterZhuJusterZhu

1.概要

C# record 是一种引用类型,是C# 9.0引入的新特性。它是一种轻量级的、不可变的数据类型,具有只读属性,因此在创建后无法更改,这使得它线程安全。与类不同,record 类型是基于值相等而不是唯一标识符的,这意味着两个 record 实例只要它们的属性相等,就被视为相等。

Records 在数据传输、模式匹配和不可变性方面非常有用。它们提供了更简洁的语法,用于创建不可变的数据对象,避免了手动实现不可变性所需的样板代码。

1.1特性

  1. 不可变性: record是不可变的,一旦创建,其状态无法修改,确保了线程安全和数据一致性。
  2. 值相等性: record实例的相等性是基于其属性的值,而不是引用。两个record对象只要它们的属性值相等,就被视为相等。
  3. 简化的语法: record提供了一种简化的语法来定义数据类型,避免了手动实现不可变性的繁琐代码。
  4. 模式匹配: record在模式匹配中非常有用,可以轻松地与模式匹配语法结合使用,简化了对数据结构的操作。
  5. 记录的层次结构: 你可以构建具有层次结构的record,这在某些情况下比使用类更为方便。
  6. 可读性: 由于其简洁的语法,record使得代码更易读,尤其是在处理大量数据传输或需要比较相等性的场景中。

1.2不可变性

不可变性的关键在于以下几点:

  1. 只读属性: record的属性默认是只读的,即它们只能在构造函数中初始化,初始化完成后就不能再修改。这确保了属性值在对象创建后不可变。
  2. init 属性: C# 9.0引入了init关键字,用于声明属性的初始化器。被init修饰的属性只能在对象初始化期间被设置,之后将变为只读,实现了不可变性。
  3. 不可变性的优势: 不可变性使得对象在多线程环境中更安全,因为它们不能被并发修改。同时,不可变对象更易于缓存和重用,提高了性能。
  4. 记录初始化: 通过构造函数或者对象初始化器进行初始化时,record类型的属性将被赋予初始值。一旦初始化完成,属性值不能再被改变。
  5. 不可变性的实现: record类型的不可变性由编译器自动生成的代码保证,确保了对象的状态不会被修改。

init关键字:

  • 通过使用record定义数据模型,结合init关键字,可以确保对象的属性在初始化后不能再被修改。

示例:

代码语言:javascript
复制
public record Person
{
    public string FirstName { get; init; }
    public string LastName { get; init; }

    public Person(string firstName, string lastName)
    {
        FirstName = firstName;
        LastName = lastName;
    }
}

// 使用record和init创建不可变对象
var person = new Person("John", "Doe");
// 以下操作是不允许的,因为属性是只读的(init关键字的作用)
// person.FirstName = "Jane"; // 编译错误

此外,还可以创建具有可变属性和字段的记录:

代码语言:javascript
复制
public record Person
{
    public required string FirstName { get; set; }
    public required string LastName { get; set; }
};

记录结构也可以是可变的,包括位置记录结构和没有位置参数的记录结构:

代码语言:javascript
复制
public record struct DataMeasurement(DateTime TakenAt, double Measurement);
代码语言:javascript
复制
public record struct Point
{
    public double X { get; set; }
    public double Y { get; set; }
    public double Z { get; set; }
}

虽然记录可以是可变的,但它们主要用于支持不可变的数据模型。 记录类型提供以下功能:

  • 用于创建具有不可变属性的引用类型的简明语法
  • 内置行为对于以数据为中心的引用类型非常有用:
    • 值相等性
    • 非破坏性变化的简明语法
    • 用于显示的内置格式设置
  • 支持继承层次结构

前面的示例展示了引用类型记录和值类型记录之间的一些区别:

  • recordrecord class 声明引用类型。 class 关键字是可选项,但可以为读者提高清晰度。 record struct 声明值类型。
  • 位置属性在 和 readonly record struct 中不可变。 它们在 中可变。

本文的其余部分将讨论 record classrecord struct 类型。 每个部分都详细说明了这些差异。 你应该在 record classrecord struct 之间作出决定,该过程类似于在 classstruct 之间作出决定。 record 一词用于描述应用于所有记录类型的行为。 record structrecord class 用于描述仅适用于 struct 或 class 类型的行为。 record 类型是在 C# 9 中推出的;record struct 类型是在 C# 10 中推出的。

1.3相等性

record类型的相等性是基于值相等性(value equality)的,意味着当两个record对象的所有属性值都相等时,它们被认为是相等的。这种相等性是自动生成的,包括Equals==!=GetHashCode方法,它们默认会比较record对象的所有属性值。

这意味着,只要两个record对象的所有属性值相等,它们就被视为相等,即使它们不是同一个对象实例。

例如:

代码语言:javascript
复制
public record Person(string FirstName, string LastName);
var person1 = new Person("John", "Doe");
var person2 = new Person("John", "Doe");

bool isEqual = person1 == person2; // 返回true,因为属性值相等

record类型的相等性使得比较对象更加直观和简便,因为你只需要关心对象的属性值是否相等,而不必担心对象实例的引用。

1.4模式匹配

模式匹配语法: 使用switch语句进行模式匹配,根据记录类型的属性值进行不同的操作。例如:

代码语言:javascript
复制
var person = new Person("Alice", 30);
switch (person)
{
    case Person p when p.Age > 18:
        Console.WriteLine($"{p.Name} is an adult.");
        break;
    case Person p:
        Console.WriteLine($"{p.Name} is a minor.");
        break;
}

模式解构: 允许在switch语句中解构记录类型,直接获取属性的值,使得代码更加简洁:

代码语言:javascript
复制
switch (person)
{
    case Person { Name: "Alice", Age: 30 }:
        Console.WriteLine("Alice is 30 years old.");
        break;
}

嵌套模式匹配: 可以在记录模式中使用其他模式,实现更复杂的匹配逻辑:

代码语言:javascript
复制
switch (person)
{
    case Person { Age: > 18, Name: var name }:
        Console.WriteLine($"{name} is an adult.");
        break;
}

模式匹配中的When条件: 可以使用when关键字添加额外的条件,使得模式匹配更加灵活:

代码语言:javascript
复制
switch (person)
{
    case Person { Age: > 18 } when person.Name.StartsWith("A"):
        Console.WriteLine($"{person.Name} is an adult and starts with 'A'.");
        break;
}

1.5 线程安全

Record类型是线程安全的。Record类型的线程安全性是通过其不可变性(immutability)来实现的。不可变性意味着一旦对象被创建,其状态就不能被修改。在Record类型中,属性是只读的,一旦对象被创建,这些属性的值就不能被修改,从而确保了对象的不可变性和线程安全性。因此,多个线程可以安全地访问和共享Record对象而无需担心数据被意外修改的问题。

例如,在C#中使用Record类型定义一个不可变的Person对象:

代码语言:javascript
复制
public record Person(string FirstName, string LastName);

在上述例子中,FirstNameLastName属性是只读的,它们的值只能在对象初始化的时候被设置,之后不能再被修改,确保了对象的不可变性和线程安全性。

使用场景

  1. 不可变性要求高: 当你需要创建不可变类型,即对象一旦创建就不能被修改时,可以使用 Record。这确保了对象的线程安全性,并且在分布式系统中特别有用。
  2. 简化数据传递: 如果你需要频繁地传递一些数据,但这些数据在传递过程中不应该被修改,Record 提供了一种简单、直观的方式来表示这些数据。
  3. 模式匹配: 在需要使用模式匹配进行数据处理的情况下,Record 类型可以提供更加清晰和简洁的模式匹配语法,用于处理各种数据情况。
  4. API 返回值: 当你需要设计 API,返回的数据对象应该是不可变的,以确保客户端无法修改这些数据,Record 是一个理想的选择。
  5. 数据传输对象(DTO): 在分布式系统中,常常需要传输数据对象,而这些对象通常是不可变的。使用 Record 类型可以简化 DTO 的定义和处理。
  6. 替代只读结构体: 如果你需要创建只读的数据结构体,Record 提供了更加简洁的语法,避免了传统只读结构体的冗长代码。

缺点

  1. 性能开销: Record 类型通常需要更多的内存,因为它们包含了用于比较对象的元数据。
  2. 不可变性带来的限制: Record 类型是不可变的,一旦创建就不能修改。这种不可变性可能在某些场景下引入不便,特别是需要频繁更新对象状态的情况。
  3. 相等性比较的复杂性: Record 类型的相等性比较是基于其属性的,而不是引用。这种方式可能在一些复杂的数据模型中引入比较的复杂性。
  4. 对象实例增多: Record 类型的不可变性意味着每次修改都会生成一个新的对象实例,这可能导致内存中存在大量的对象实例。

这些缺点并不意味着 Record 类型不可用,而是在选择使用 Record 类型时需要权衡其优势和缺点,以便选择最适合特定场景的数据模型。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2023-10-12,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 JusterZhu 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1.概要
    • 1.1特性
      • 1.2不可变性
        • 1.3相等性
          • 1.4模式匹配
            • 1.5 线程安全
              • 使用场景
                • 缺点
                领券
                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档