C#记录教程展示了如何在C#中使用记录类型。
记录是一种引用类型,其主要目的是保存数据。这对于数据分析非常有用。记录类型简化了代码并提高了可读性,并删除了不必要的样板文件。
记录类型提供以下功能:
- 以数据为中心的对象的简洁语法
- 不可变数据的简洁语法
- 基于值的相等性
- 非破坏性变异的简洁语法
- 内置打印格式
编译器创建了Object.Equals(Object)
和Object.GetHashCode
的覆盖。它为==
和!=
运算符创建方法并实现System.IEquatable
。记录还提供了对Object.ToString
的覆盖。
虽然记录与标准类有很多相似之处,但它们有不同的用途。类用于定义对象的复杂层次结构及其职责,记录擅长为分析目的存储数据。
重要的函数式语言,例如F#和Clojure,从一开始就有记录类型。记录类型出现在C#9.0中。
C#记录位置语法
创建记录的最简单方法是使用位置语法。
record User(string FirstName, string LastName, string occupation);
使用此语法,编译器会自动创建以下内容:
- 记录声明中提供的每个位置参数的公共初始自动实现属性。
- 一个主构造函数,其参数与记录声明中的位置参数相匹配
- 一个
Deconstruct
方法,为记录声明中提供的每个位置参数提供一个out参数
使用位置语法创建的数据是不可变的。数据不变性是函数式编程的重要基石。
C#记录简单例子
在下面的例子中,我们创建了一个简单的记录类型。
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
记录;我们准备好使用它了。
users.ForEach(Console.WriteLine);
我们遍历用户列表并将它们打印到控制台。由于记录的内置格式,我们有一个人类可读的记录输出。
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的记录类型。
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表达式按颜色对汽车进行分组。同样,我们在一行中定义了记录类型。我们专注于数据分析,而不是复杂的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#记录相等
记录提供基于价值的平等。当我们专注于数据分析时,这是预期的行为。另一方面,类默认提供引用相等性。要与类实现基于值的相等性,我们必须向类定义添加额外的代码行,这通常使用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; } }
在示例中,我们比较具有相同数据的两条记录和两个类对象。
$ dotnet run True False
记录比较值,而类对象引用。第二个输出为False,因为p1
和p2
指向内存中的两个不同对象。
C#记录解构
通过位置语法,我们可以自动实现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);
记录的属性可以很容易地通过解构操作分离成变量。
$ dotnet run John Doe earns 980 per month
C#记录无损变异
在函数式编程中,我们使用不可变数据。当我们需要修改数据时,我们会创建原始数据的修改副本,而这些副本是完整的。这个简单的规则在并发编程中带来了巨大的好处。
我们可以使用with
关键字来获取我们记录的修改副本。
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#可变记录
可以创建可变记录。但是,如果可能,最好使用不可变记录。
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!; };
通过实现我们自己的属性,我们有一个可变记录。
$ dotnet run User { FirstName = John, LastName = Doe, Occupation = gardener } User { FirstName = John, LastName = Doe, Occupation = driver }
网页抓取示例
为了演示记录的有用性,我们有一个更复杂的示例,它从网站上抓取数据。
$ 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
记录,其中存储一行解析后的数据。
在本文中,我们介绍了C#记录类型。
列出所有C#教程。