C# record
最后修改于 2023 年 7 月 5 日
C# record 教程展示了如何在 C# 中使用 record 类型。
Record 是一种引用类型,其主要目的是保存数据。 它对于数据分析非常有用。 record 类型简化了代码并提高了其可读性,并消除了不必要的样板代码。
record 类型提供以下功能:
- 用于以数据为中心的对象简洁的语法
- 用于不可变数据的简洁语法
- 基于值的相等性
- 用于非破坏性突变的简洁语法
- 用于打印的内置格式化
编译器会创建 Object.Equals(Object)
和 Object.GetHashCode
的重写。 它会创建用于 ==
和 !=
运算符的方法,并实现 System.IEquatable<T>
。 Records 还提供了 Object.ToString
的重写。
虽然 records 与标准类有很多相似之处,但它们的目的不同。 类用于定义对象的复杂层次结构及其职责,而 records 擅长存储数据以进行分析。
重要的函数式语言,例如 F# 和 Clojure,自诞生以来就具有 record 类型。 record 类型出现在 C# 9.0 中。
C# record 位置语法
创建 record 的最简单方法是使用位置语法。
record User(string FirstName, string LastName, string occupation);
使用此语法,编译器会自动创建以下内容:
- 为 record 声明中提供的每个位置参数创建一个公共的 init-only 自动实现的属性。
- 一个主构造函数,其参数与 record 声明中的位置参数匹配
- 一个
Deconstruct
方法,其中包含 record 声明中提供的每个位置参数的 out 参数
使用位置语法创建的数据是不可变的。 数据不变性是函数式编程的重要基石。
C# record 简单示例
在以下示例中,我们创建一个简单的 record 类型。
var users = new List<User> { new ("John", "Doe", 1230), new ("Lucy", "Novak", 670), new ("Ben", "Walter", 2050), new ("Robin", "Brown", 2300), new ("Amy", "Doe", 1250), new ("Joe", "Draker", 1190), new ("Janet", "Doe", 980), new ("Albert", "Novak", 1930), }; users.ForEach(Console.WriteLine); Console.WriteLine(users[0].FirstName); Console.WriteLine(users[0].LastName); Console.WriteLine(users[0].Salary); record User(string FirstName, string LastName, int Salary);
我们在一行中创建了一个 User
record; 我们已准备好使用它。
users.ForEach(Console.WriteLine);
我们遍历用户列表并将它们打印到控制台。 感谢 records 的内置格式化,我们拥有 records 的人类可读的输出。
Console.WriteLine(users[0].FirstName); Console.WriteLine(users[0].LastName); Console.WriteLine(users[0].Salary);
这些属性会自动创建。
$ dotner run User { FirstName = John, LastName = Doe, Salary = 1230 } User { FirstName = Lucy, LastName = Novak, Salary = 670 } User { FirstName = Ben, LastName = Walter, Salary = 2050 } User { FirstName = Robin, LastName = Brown, Salary = 2300 } User { FirstName = Amy, LastName = Doe, Salary = 1250 } User { FirstName = Joe, LastName = Draker, Salary = 1190 } User { FirstName = Janet, LastName = Doe, Salary = 980 } User { FirstName = Albert, LastName = Novak, Salary = 1930 } John Doe 1230
在第二个示例中,我们使用带有 LINQ 的 record 类型。
var cars = new List<Car> { new ("Audi", "red", 52642), new ("Mercedes", "blue", 57127), new ("Skoda", "black", 9000), new ("Volvo", "red", 29000), new ("Bentley", "yellow", 350000), new ("Citroen", "white", 21000), new ("Hummer", "black", 41400), new ("Volkswagen", "white", 21600), }; var groups = from car in cars group car by car.Colour; foreach (var group in groups) { Console.WriteLine(group.Key); foreach (var car in group) { Console.WriteLine($" {car.Name} {car.Price}"); } } record Car(string Name, string Colour, int Price);
我们使用 LINQ 表达式按颜色对汽车进行分组。 同样,我们在一行中定义了 record 类型。 我们专注于数据分析,而不是复杂的 OOP 技术。
$ dotnet run red Audi 52642 Volvo 29000 blue Mercedes 57127 black Skoda 9000 Hummer 41400 yellow Bentley 350000 white Citroen 21000 Volkswagen 21600
C# record 相等性
Records 提供基于值的相等性。 当我们专注于数据分析时,这是预期的行为。 另一方面,类默认提供引用相等性。 要使用类实现基于值的相等性,我们必须向类定义添加额外的代码行,这通常通过 IDE 生成器完成,并被认为是样板代码。
var u1 = new User("John", "Doe", "gardener"); var u2 = new User("John", "Doe", "gardener"); Console.WriteLine(u1 == u2); var p1 = new Person("Roger", "Roe", "driver"); var p2 = new Person("Roger", "Roe", "driver"); Console.WriteLine(p1 == p2); record User(string FirstName, string LastName, string occupation); class Person { public Person(string firstName, string lastName, string occupation) { FirstName = firstName; LastName = lastName; Occupation = occupation; } public string FirstName { get; set; } public string LastName { get; set; } public string Occupation { get; set; } }
在此示例中,我们比较了具有相同数据的两个 records 和两个类对象。
$ dotnet run True False
Records 比较值,而类对象比较引用。 第二个输出为 False,因为 p1
和 p2
指向内存中的两个不同对象。
C# record 解构
使用位置语法,我们自动实现了 Deconstruct
方法。
var u = new User("John", "Doe", 980); (string fname, string lname, int sal) = u; Console.WriteLine($"{fname} {lname} earns {sal} per month"); record User(string FirstName, string LastName, int Salary);
通过解构操作,可以将 record 的属性轻松地分离到变量中。
$ dotnet run John Doe earns 980 per month
C# record 非破坏性突变
在函数式编程中,我们使用不可变数据。 当我们需要修改数据时,我们会创建原始数据的修改副本,该副本完好无损。 这个简单的规则为并发编程带来了巨大的好处。
我们可以使用 with
关键字来获取 record 的修改副本。
var users = new List<User> { new ("John", "Doe", 1230), new ("Lucy", "Novak", 670), new ("Ben", "Walter", 2050), new ("Robin", "Brown", 2300), new ("Amy", "Doe", 1250), new ("Joe", "Draker", 1190), new ("Janet", "Doe", 980), new ("Albert", "Novak", 1930), }; var users2 = new List<User>(); users.ForEach(u => users2.Add(u with { Salary = u.Salary + 200 })); users.ForEach(Console.WriteLine); Console.WriteLine("---------------"); users2.ForEach(Console.WriteLine); record User(string FirstName, string LastName, int Salary);
在此示例中,我们有一个用户列表。 我们想给每个用户添加奖金。 我们没有修改原始列表,而是创建了一个新的列表,其中他们的工资已修改。
$ dotnet run User { FirstName = John, LastName = Doe, Salary = 1230 } User { FirstName = Lucy, LastName = Novak, Salary = 670 } User { FirstName = Ben, LastName = Walter, Salary = 2050 } User { FirstName = Robin, LastName = Brown, Salary = 2300 } User { FirstName = Amy, LastName = Doe, Salary = 1250 } User { FirstName = Joe, LastName = Draker, Salary = 1190 } User { FirstName = Janet, LastName = Doe, Salary = 980 } User { FirstName = Albert, LastName = Novak, Salary = 1930 } --------------- User { FirstName = John, LastName = Doe, Salary = 1430 } User { FirstName = Lucy, LastName = Novak, Salary = 870 } User { FirstName = Ben, LastName = Walter, Salary = 2250 } User { FirstName = Robin, LastName = Brown, Salary = 2500 } User { FirstName = Amy, LastName = Doe, Salary = 1450 } User { FirstName = Joe, LastName = Draker, Salary = 1390 } User { FirstName = Janet, LastName = Doe, Salary = 1180 } User { FirstName = Albert, LastName = Novak, Salary = 2130 }
C# 可变 record
可以创建一个可变 record。 但是,如果可能,最好使用不可变 record。
var u = new User("John", "Doe", "gardener"); Console.WriteLine(u); u.Occupation = "driver"; Console.WriteLine(u); record User { public User(string firstName, string lastName, string occupation) { FirstName = firstName; LastName = lastName; Occupation = occupation; } public string FirstName { get; set; } = default!; public string LastName { get; set; } = default!; public string Occupation { get; set; } = default!; };
通过实现我们自己的属性,我们得到一个可变 record。
$ dotnet run User { FirstName = John, LastName = Doe, Occupation = gardener } User { FirstName = John, LastName = Doe, Occupation = driver }
网络抓取示例
为了演示 records 的实用性,我们有一个更复杂的示例,它从网站上抓取数据。
$ dotnet add package AngleSharp $ dotnet add package CsvHelper
我们需要将 AngleSharp
和 CsvHelper
包添加到项目中。
using System.Collections.Generic; using System.Globalization; using AngleSharp; using CsvHelper; var config = Configuration.Default.WithDefaultLoader(); using var context = BrowsingContext.New(config); var url = "https://nrf.com/resources/top-retailers/top-100-retailers/top-100-retailers-2019"; using var doc = await context.OpenAsync(url); var htable = doc.GetElementById("stores-list--section-16266"); var trs = htable.QuerySelectorAll("tr").Skip(1); var csvConfig = new CsvHelper.Configuration.CsvConfiguration(CultureInfo.CurrentCulture) { ShouldQuote = args => false }; using var fs = new StreamWriter("data.csv"); using var writer = new CsvWriter(fs, csvConfig); var rows = new List<Row>(); foreach (var tr in trs) { var tds = tr.QuerySelectorAll("td").Take(3); var fields = (from e in tds select e.TextContent).ToArray(); var row = new Row(fields[0], fields[1], fields[2]); rows.Add(row); } writer.WriteRecords(rows); record Row(string Rank, string Company, string Sales);
在此示例中,我们从网站上抓取数据。 在 HTML 表格中,有美国排名前 100 的零售商。 我们连接到该网站,解析 HTML 表格,并选择其中的三列。 解析后的数据保存到 CSV 文件中。 该示例创建了 Row
record,用于存储解析后数据的一行。
来源
在本文中,我们介绍了 C# record 类型。
作者
列出所有 C# 教程。