切换导航
简历
工具
演示
查询
导航菜单
服务端
30
前端
114
数据库
82
进阶开发
8
桌面程序
2
QA
15
软件使用
36
规范协议
5
操作系统
11
AI
0
复用资源
6
硬件
0
标签
C# 基础
Mybatis
ADO.NET
ASP.NET
Python 基础
Linq
Web Forms
.NET
Spring
SpringBoot
SpringCloud
.NET Core 中间件
Maven
.NET 类库
ASP.NET MVC
EF
NHibernate
WebApi
Java
外部链接
.NET Core SDK和运行时安装包
淘宝 NPM 镜像
查询网
Tables Generator线上表格转换工具
引用cdn在线脚本和样式
首页
服务端
C# 记录修饰符 record 介绍与使用
[TOC] ## 1. C# 记录类型 + 记录类型的声明需要使用记录修饰符 record,record修饰符用于创建不可变的数据类型 + 记录类型用来提供用于封装数据的内置功能,它自动为我们生成了一些常用的方法,如Equals、GetHashCode和ToString等 + 记录类型它与类相似,它有主构造函数,在记录类型中的主构造函数中的参数被称之为位置参数,主构造函数中的参数会被编译器编译成记录类型的属性 **记录可分为以下两类** ### 1.1. 引用类型记录 类记录类型的 record 记录类型,这类属于引用类型,位置参数编译成的属性为必须(required)的only-init属性,只有初始化的时候可赋值,并必须赋值 ```csharp // 声明了一个记录类型Person,它有两个参数(位置参数) public record Person(string FirstName, string LastName); // 下面的调用是错误的,因为它未初始化位置参数 Person p = new Person(); // CS7036: 未提供与“RecordUseExample.Person.Person(string, int)"的所需参数”name" ``` 上面记录类型的声明与下面这段代码效果是一致的,上面是下面的简写 ```csharp public record Person { public required string FirstName { get; init; } public required string LastName { get; init; } }; ``` <hr> > <span style="color:#f98110">默认情况下record 记录类型是不可变的类型,但是非得让它可变也是可以的,只要修改属性的 init 访问器即可,将 init 改成 set,这种可变的 record 记录类型就不能简写了,只能展开按类的写法写,将 class 改成 record 即可</span> >```csharp public record Person { public required string FirstName { get; set; } public required string LastName { get; set; } }; ``` ### 1.2. 值类型记录 结构记录类型 readonly record struct 记录类型,属于值类型,位置参数编译成的属性为only-init属性,只有初始化的时候可赋值 ```csharp // 声明了一个结构记录类型Point,它有三个参数(位置参数),同样它 public readonly record struct Point(double X, double Y, double Z); ``` 上面记录类型的声明与下面这段代码效果是一致的,上面是下面的简写 ```csharp public record struct Point { public double X { get; init; } public double Y { get; init; } public double Z { get; init; } } ``` <hr> > <span style="color:#f98110">默认情况下 record struct 记录类型是不可变的类型,但让它可变也是可以的,详细查看下面示例</span> >```csharp // 方式二:简写时不加关键字 readonly public record struct DataMeasurement(DateTime TakenAt, double Measurement); // 方式一:修改属性的 init 访问器即可,将 init 改成 set public record struct Point { public double X { get; set; } public double Y { get; set; } public double Z { get; set; } } ``` <span style="color:#CD0E0E">**虽然记录可以是可变的,但它们主要用于支持不可变的数据模型**</span> ## 2. 记录类型提供以下功能 ### 2.1. 属性定义的位置语法 位置语法:<mark>访问修饰符 record 记录命名([位置参数……])</mark> ```csharp // 使用位置语法声明了一个引用记录 public record Person(string name, int age); public static string Exmaple() { // 在创建实例时,可以使用位置参数来声明记录的属性,并初始化属性值 Person s = new("曹孟德", 56); return $"{new StackTrace(true).GetFrame(0).GetMethod().Name}:[{s.name}],[{s.age}]"; } ``` |**为属性定义使用位置语法时,编译器将创建以下内容**| |--| |**1. **为记录声明中提供的每个位置参数提供一个公共的自动实现的属性| |**∟1.1. **对于 record 和 readonly record struct 类型,为 record 属性| |**∟1.2. **对于 record struct 类型,为读写属性| |**2. **主构造函数,它的参数与记录声明上的位置参数匹配| |**3. **对于 record struct 类型,则是将每个字段设置为其默认值的无参数构造函数| |**4. **一个 Deconstruct 方法,对记录声明中提供的每个位置参数都有一个 out 参数。 此方法解构了使用位置语法定义的属性;它忽略了使用标准属性语法定义的属性| 如何向编译器根据记录定义创建的属性添加特性?这等于是向应用于`位置记录属性`添加目标特性,下面的示例给出了语法,语法 ```csharp // property: 为位置参数(编译器创建的属性)添加特性 public record Person([property: JsonPropertyName("firstName")] string FirstName, [property: JsonPropertyName("lastName")] string LastName); ``` > property: 目标指示该特性应用于编译器生成的属性。其他值包括将该特性应用于字段的 field:,以及将该特性应用于参数的 param: #### 2.1.2. 位置参数的应用 + 如果生成的自动实现的属性定义并不是你所需要的,你可以自行定义同名的属性。 例如,你可能想要更改可访问性或可变性,或者为 get 或 set 访问器提供实现。 <p></p> ```csharp public record Per(string Name, int Age) { internal int Age { get; set; } = Age; } public static string Exmaple2() { Per s = new("曹孟德", 56); // s.Name = "刘备"; // 这句话报错:因为根据位置参数生成的属性默认只有创建时可以写 s.Age = 60; // 这里没有报错是因为记录在修改了Age属性的init访问器,修改为set后变可写了 return $"{new StackTrace(true).GetFrame(0).GetMethod().Name}:[{s.Name}],[{s.Age}]"; } ``` + 如果在定义记录时声明了属性,那么在创建记录实例时必须使用位置参数为这些属性进行初始化。位置参数是按照属性在声明时的顺序提供的参数,用于对属性进行初始化 <p></p> ```csharp record Person(string Name, int Age); // 定义一个记录类型,具有Name和Age两个属性 Person person = new Person("John", 30); // 使用位置参数初始化Name和Age属性 ``` + 如果在定义记录时使用了自动实现属性(即只有get和set访问器,没有显式定义的字段),那么在创建记录实例时必须为这些属性提供初始化值 <p></p> ```csharp record Per { public Per(int age) { Age = age; // 初始化Age属性 } internal int Age { get; init; } } public static void Exmaple3() { Per2 s = new(66); } ``` + 如果在定义记录时添加了支持字段(即显式声明的字段),那么在创建记录实例时必须为这些字段提供初始化值 <p></p> ```csharp record Person { public string Name { get; init; } // 自动实现属性 private int _age; // 支持字段 public Person(string name, int age) { Name = name; // 初始化Name属性 _age = age; // 初始化支持字段_age } public int GetAge() => _age; } Person person = new Person("John", 30); // 初始化Name属性为"John",_age字段为30 int age = person.GetAge(); // 获取_age字段的值,返回30 ``` ### 2.2. 不可变性 positional record 和 positional readonly record struct 声明 init-only 属性 。 positional record struct 声明 read-write 属性 init-only 属性无论是通过位置参数(record class 和 readonly record struct)创建的,还是通过指定 init 访问器创建的,都具有不可变性,初始化后,将不能更改`值型属性的值`或`引用型属性的引用`。 不过,引用型属性引用的数据是可以更改的。 下面的示例展示了引用型不可变属性的内容(本例中是数组)是可变的 ```csharp public record Person(string FirstName, string LastName, string[] PhoneNumbers); public static void Main() { Person person = new("Nancy", "Davolio", new string[1] { "555-1234" }); Console.WriteLine(person.PhoneNumbers[0]); // 输出: 555-1234 person.PhoneNumbers[0] = "555-6789"; // person.PhoneNumbers是个数组类型,它的值可以变更 Console.WriteLine(person.PhoneNumbers[0]); // 输出: 555-6789 } ``` > 记录类型特有的功能是由编译器合成的方法实现的,这些方法都不会通过修改对象状态来影响不可变性。 除非另行指定,否则将为 record、record struct 和 readonly record struct 声明生成综合方法,例如Equals和ToString方法。 ### 2.3. 值相等性 如果不替代或替换相等性方法,则声明的类型将控制如何定义相等性: + 对于 class 类型,如果两个对象引用内存中的同一对象,则这两个对象相等。 + 对于 struct 类型,如果两个对象是相同的类型并且存储相同的值,则这两个对象相等。 + 对于具有 record 修饰符(record class、record struct 和 readonly record struct)的类型,如果两个对象是相同的类型并且存储相同的值,则这两个对象相等。 record struct 的相等性定义与 struct 的相等性定义相同。 不同之处在于,对于 struct,实现处于 ValueType.Equals(Object) 中并且依赖反射。 对于记录,实现由编译器合成,并且使用声明的数据成员。 一些数据模型需要引用相等性。 例如,Entity Framework Core 依赖于引用相等性来确保它对概念上是一个实体的实体类型只使用一个实例。 因此,记录和记录结构不适合用作 Entity Framework Core 中的实体类型。 下面的示例说明了记录类型的值相等性: ```csharp public record Person(string FirstName, string LastName, string[] PhoneNumbers); public static void Main() { var phoneNumbers = new string[2]; Person person1 = new("Nancy", "Davolio", phoneNumbers); Person person2 = new("Nancy", "Davolio", phoneNumbers); Console.WriteLine(person1 == person2); // 类型和值都相等 person1.PhoneNumbers[0] = "555-1234"; Console.WriteLine(person1 == person2); // 类型和值都相等(虽然 person1的PhoneNumbers赋值了,但PhoneNumbers是数组类型,person1和person2引用的同一个数组,因此值也是相同的) Console.WriteLine(ReferenceEquals(person1, person2)); // 引用不相等 } ``` > **Tips & Verify:**为了实现值相等性,编译器合成了几种方法,包括: > + Object.Equals(Object) 的替代。 如果显式声明此替代,则会出现错误。 当两个参数都为非 NULL 时,此方法被用作 Object.Equals(Object, Object) 静态方法的基础。 > + virtual 或 sealed 的 Equals(R? other),其中 R 是记录类型。 此方法实现 `IEquatable<T>`。可以显式声明此方法。 > + 如果记录类型派生自基础记录类型 Base,则为 Equals(Base? other)。 如果显式声明此替代,则会出现错误。 如果提供自己的 Equals(R? other) 实现,则还应提供 GetHashCode 的实现。 > + Object.GetHashCode() 的替代。 可以显式声明此方法。 > + 运算符 == 和 != 的替代。 如果显式声明这些运算符,则会出现错误。 > + 如果记录类型派生自基础记录类型,则为 protected override Type EqualityContract { get; };。 可以显式声明此属性。 有关详细信息,请参阅继承层次结构中的相等性。 > 如果记录类型的方法与允许显式声明的合成方法的签名匹配,则编译器不会合成该方法。 ### 2.4. 非破坏性变化 如果需要复制包含一些修改的实例,可以使用 with 表达式来实现非破坏性变化。 with 表达式创建一个新的记录实例,该实例是现有记录实例的一个副本,修改了指定属性和字段。 使用对象初始值设定项语法来指定要更改的值,如以下示例中所示: ```csharp public record Person(string FirstName, string LastName) { public string[] PhoneNumbers { get; init; } } public static void Main() { Person person1 = new("Nancy", "Davolio") { PhoneNumbers = new string[1] }; Console.WriteLine(person1); // 输出: Person { FirstName = Nancy, LastName = Davolio, PhoneNumbers = System.String[] } Person person2 = person1 with { FirstName = "John" }; Console.WriteLine(person2); // 输出: Person { FirstName = John, LastName = Davolio, PhoneNumbers = System.String[] } Console.WriteLine(person1 == person2); // output: False person2 = person1 with { PhoneNumbers = new string[1] }; Console.WriteLine(person2); // 输出: Person { FirstName = Nancy, LastName = Davolio, PhoneNumbers = System.String[] } Console.WriteLine(person1 == person2); // 输出: False,这里虽然person1和person2打印出的结果一样但PhoneNumbers这引用并不是同一个因此它们不相等 person2 = person1 with { }; // 通过with复制的实例,没有对它进行任何的操作,因此它们是相等的 Console.WriteLine(person1 == person2); // 输出: True } ``` + with 表达式可以设置位置属性或使用标准属性语法创建的属性。 显式声明属性必须有一个 init 或 set 访问器才能在 with 表达式中进行更改。 + with 表达式的结果是一个浅的副本,这意味着对于引用属性,只复制对实例的引用。 原始记录和副本最终都具有对同一实例的引用。 > **Tips & Verify:** 为了对 record class 类型实现此功能,编译器合成了一个克隆方法和一个复制构造函数。 虚拟克隆方法返回由复制构造函数初始化的新记录。 当使用 with 表达式时,编译器将创建调用克隆方法的代码,然后设置 with 表达式中指定的属性。 > 如果你需要不同的复制行为,可以在 record class 中编写自己的复制构造函数。 如果这样做,编译器不会合成方法。 如果记录是 sealed,则使构造函数为 private,否则使其为 protected。 编译器不会针对 record struct 类型合成复制构造函数。 虽然你可以编写一个方法,但编译器不会为 with 表达式生成对它的调用。 record struct 的值在分配时进行复制。 > 你不能替代克隆方法,也不能在任意记录类型中创建名为 Clone 的成员。 克隆方法的实际名称是由编译器生成的。 ### 2.5. 用于显示的内置格式设置 记录类型具有编译器生成的 ToString 方法,可显示公共属性和字段的名称和值。 ToString 方法返回一个格式如下的字符串:`<record type name> { <property name> = <value>, <property name> = <value>, ...}`,为 `<value>` 打印的字符串是 ToString() 针对属性类型返回的字符串。 在以下示例中,ChildNames 是 System.Array,其中 ToString 返回 System.String[]: ```csharp public record Person(string FirstName, string LastName) { public string[] PhoneNumbers { get; init; } //public override string ToString() //{ // return this.FirstName + " - " + this.LastName; //} } public static void Exmaple() { Person person = new("Nancy", "Davolio") { PhoneNumbers = new string[1] }; Console.WriteLine(person); // 输出: Person { FirstName = Nancy, LastName = Davolio, PhoneNumbers = System.String[] } // 将记录中的重载ToString()代码注释去掉后 // 输出: Nancy - Davolio } ``` 为了实现此功能,编译器在 record class 类型中合成了一个虚拟 PrintMembers 方法和一个 ToString 替代。 此成员在 record struct 类型中为 private。 ToString 替代创建了一个 StringBuilder 对象,它的类型名称后跟一个左括号。 它调用 PrintMembers 以添加属性名称和值,然后添加右括号。 下面的示例展示了类似于合成替代中包含的代码: ```csharp public override string ToString() { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append("Teacher"); // type name stringBuilder.Append(" { "); if (PrintMembers(stringBuilder)) { stringBuilder.Append(" "); } stringBuilder.Append("}"); return stringBuilder.ToString(); } ``` 你可以提供自己的 PrintMembers 实现或 ToString 替代。 在 C# 10 及更高版本中,ToString 的实现有可能包含 sealed 修饰符,这会阻止编译器为任何派生记录合成一个 ToString 实现。 可以在 record 类型的层次结构中创建一致的字符串表示形式。 (派生记录仍会为所有派生属性生成一个 PrintMembers 方法。) ### 2.6. 继承 仅适用于 record class 类型,一条记录可以从另一条记录继承。 但是,记录不能从类继承,类也不能从记录继承。 #### 2.6.1. 派生记录类型中的位置参数 派生记录为基本记录主构造函数中的所有参数声明位置参数。 基本记录声明并初始化这些属性。 派生记录不会隐藏它们,而只会创建和初始化未在其基本记录中声明的参数的属性 ```csharp public record Persons(string FirstName, string LastName,int Age); public record Teacher(string FirstName, string LastName, int Age, int Grade) : Persons(FirstName, LastName, Age); // 上面Teacher记录继承了Persons,基记录有多少个位置参数,派生记录必须同样有这些位置参数,只能多不能少,示例中派生记录就比基记录多了个Grade参数 public static string Example5() { Persons teacher = new Teacher("Nancy", "Davolio",58, 3); // 初始化时,基记录的位置参数也被输出化了,派生记录的独有属性也被初始化了 Console.WriteLine(teacher); // 输出: Teacher { FirstName = Nancy, LastName = Davolio, Age = 58, Grade = 3 } return message; } ``` #### 2.6.2. 继承层次结构中的相等性 本部分适用于 record class 类型,但不适用于 record struct 类型。 要使两个记录变量相等,运行时类型必须相等。 变量声明的类型可以不同。 下面的代码示例演示了继承的相等性比较: ```csharp public abstract record Person(string FirstName, string LastName); public record Teacher(string FirstName, string LastName, int Grade) : Person(FirstName, LastName); public record Student(string FirstName, string LastName, int Grade) : Person(FirstName, LastName); public static void Main() { Person teacher = new Teacher("Nancy", "Davolio", 3); Person student = new Student("Nancy", "Davolio", 3); Console.WriteLine(teacher == student); // 输出: False //在上面的比较中,teacher和student都声明为Person类型的变量,并且属性相同,属性值也相同,但它们并不相等,因为运行时的类型分别是Teacher和Student Student student2 = new Student("Nancy", "Davolio", 3); Console.WriteLine(student2 == student); // output: True //上面的比较,虽然声明的是不同的类型,但它们运行时的类型相同,在属性和属性值有相同的情况下,它们就是相等的 } ``` 相等性测试依赖于实际对象的运行时类型,而不是声明的变量类型 > **Tips & Verify:**为实现此行为,编译器合成了一个 EqualityContract 属性,该属性返回一个与记录类型匹配的 Type 对象。 利用 EqualityContract相等性方法在检查相等性时,就可以比较对象的运行时类型。 如果记录的基类型为 object,则此属性为 virtual。 如果基类型是其他记录类型,则此属性是一个替代。 如果记录类型为 sealed,则此属性实际上是 sealed,因为类型是 sealed。 > 在代码比较派生类型的两个实例时,合成的相等性方法会检查基类型和派生类型的所有数据成员是否相等。 合成的 GetHashCode 方法从基类型和派生的记录类型中声明的所有数据成员中使用 GetHashCode 方法。 record 的数据成员包括所有声明的字段,以及任何自动实现属性的编译器合成支持字段。 #### 2.6.3. 派生记录中的 with 表达式 with表达式的结果与该表达式的操作数具有相同的运行时类型。运行时类型的所有属性都会被复制,但您只能设置编译时类型的属性,如下例所示: ```csharp public record Point2(int X, int Y) { public int Zbase { get; set; } }; public record NamedPoint(string Name, int X, int Y) : Point2(X, Y) { public int Zderived { get; set; } }; public static void Example6() { Point2 p1 = new NamedPoint("A", 1, 2) { Zbase = 3, Zderived = 4 }; Point2 p2 = p1 with { X = 5, Y = 6, Zbase = 7 }; // 在这句的大括号内添加Name属性会报错,虽然p1运行时实际上是NamedPoint,但编译器编译时p1是Point2类型,因此它不存在Name属性 Console.WriteLine(p2 is NamedPoint); // 输出: True,(p2虽然申明的是Point2,但它实际上是NamedPoint) Console.WriteLine(p2); // 输出: NamedPoint { X = 5, Y = 6, Zbase = 7, Name = A, Zderived = 4 } Point2 p3 = (NamedPoint)p1 with { Name = "B", X = 5, Y = 6, Zbase = 7, Zderived = 8 }; // 如果对p1进行了类型转换,转成NamedPoint后,编译器就允许对Name属性进行初始化了 Console.WriteLine(p3); // 输出: NamedPoint { X = 5, Y = 6, Zbase = 7, Name = B, Zderived = 8 } } ``` #### 2.6.4. 派生记录中的 PrintMembers 格式设置 派生记录类型的合成 PrintMembers 方法调用基实现。 结果是派生类型和基类型的所有公共属性和字段都包含在 ToString 输出中,如以下示例中所示: ```csharp public abstract record Person(string FirstName, string LastName); public record Teacher(string FirstName, string LastName, int Grade) : Person(FirstName, LastName); public record Student(string FirstName, string LastName, int Grade) : Person(FirstName, LastName); public static void Main() { Person teacher = new Teacher("Nancy", "Davolio", 3); Console.WriteLine(teacher); // 输出: Teacher { FirstName = Nancy, LastName = Davolio, Grade = 3 } } ``` **自己实现 PrintMembers 方法** + 对于派生自 object 的 sealed 记录(不声明基本记录):使用 private bool PrintMembers(StringBuilder builder) <p></p> ```csharp public sealed record PersonMemberSealed(string FirstName, string LastName, string[] PhoneNumbers) { private bool PrintMembers(StringBuilder stringBuilder) { stringBuilder.Append($"[FirstName = {FirstName}], [LastName = {LastName}],"); stringBuilder.Append($"[PhoneNumber1 = {PhoneNumbers[0]}], [PhoneNumber2 = {PhoneNumbers[1]}]"); return true; } } public static string Example11() { PersonMemberSealed person = new PersonMemberSealed("关羽", "潘凤", new string[2] { "555-1234", "555-6789" }); Console.WriteLine(person); // 输出: PersonMemberSealed { [FirstName = 关羽], [LastName = 潘凤],[PhoneNumber1 = 555-1234], [PhoneNumber2 = 555-6789] } return message; } ``` > **Tips:**如果记录是封闭记录,并且是基记录类型时,实现PrintMembers方法必须是私有的非虚拟方法,否则会报以下错误 CS0549:"RecordUseExample.PersonMstring BuildererSealed.PrintMembers(StringBuilder)“是密封类型“记录使用范例个人成员已密封”中新的虚拟成员 CS8879:记录成员“RecordUseExample.PersonMstring BuildererSealed.PrintMembers(StringBuilder)”必须是非公开的。 CS0621:“RecordUseExample.PersonMemberSealed.PrintMembers(StringBuilder)":虚成员或抽象成员不能是私有的 + 对于非 sealed 且派生自对象的记录:使用 protected virtual bool PrintMembers(StringBuilder builder) <p></p> ```csharp public record PersonMember(string FirstName, string LastName, string[] PhoneNumbers) { protected virtual bool PrintMembers(StringBuilder stringBuilder) { stringBuilder.Append($"[FirstName = {FirstName}], [LastName = {LastName}],"); stringBuilder.Append($"[PhoneNumber1 = {PhoneNumbers[0]}], [PhoneNumber2 = {PhoneNumbers[1]}]"); return true; } } public static string Example() { PersonMember teacher = new PersonMember("关羽", "潘凤", new string[2] { "555-1234", "555-6789" }); Console.WriteLine(teacher); // 输出: PersonMember { [FirstName = 关羽], [LastName = 潘凤],[PhoneNumber1 = 555-1234], [PhoneNumber2 = 555-6789] } return message; } ``` > **Tips:** CS8875: 必须保护记录成员“RecordUseExample.PersonMember.PrintMembers(StringBuilder)" + 对于非 sealed 且派生自其他记录的记录:使用 protected override bool PrintMembers(StringBuilder builder) <p></p> ```csharp public record TeacherMember(string FirstName, string LastName, string[] PhoneNumbers, int Grade) : PersonMember(FirstName, LastName, PhoneNumbers) { protected override bool PrintMembers(StringBuilder stringBuilder) { if (base.PrintMembers(stringBuilder)) { stringBuilder.Append(", "); }; stringBuilder.Append($"[Grade = {Grade}]"); return true; } }; public static string Example() { PersonMember teacher = new TeacherMember("关羽", "潘凤", new string[2] { "555-1234", "555-6789" }, 3); Console.WriteLine(teacher); // 输出: TeacherMember { [FirstName = 关羽], [LastName = 潘凤],[PhoneNumber1 = 555-1234], [PhoneNumber2 = 555-6789], [Grade = 3] } return message; } ``` + 对于从另一个记录派生的 sealed 记录(注意封闭类型是 sealed,因此该方法实际上是 sealed):protected override bool PrintMembers(StringBuilder builder) <p></p> ```csharp public sealed record TeacherMemberSealed(string FirstName, string LastName, string[] PhoneNumbers, int Grade) : PersonMember(FirstName, LastName, PhoneNumbers) // 注意这里继承的是第2点的PersonMember { protected override bool PrintMembers(StringBuilder stringBuilder) // 基记录约束了这里只能使用protected override { if (base.PrintMembers(stringBuilder)) { stringBuilder.Append(", "); }; stringBuilder.Append($"[Grade = {Grade}]"); return true; } } public static string Example() { PersonMember teacher = new TeacherMemberSealed("关羽", "潘凤", new string[2] { "555-1234", "555-6789" }, 3); Console.WriteLine(teacher); // 输出: TeacherMemberSealed { [FirstName = 关羽], [LastName = 潘凤],[PhoneNumber1 = 555-1234], [PhoneNumber2 = 555-6789], [Grade = 3] } return message; } ``` <hr/> > **Tips:** + 以上四点总结就是PrintMembers方法必须是本记录中受保护的,不能直接使用public,作为可被继承的基记录且允许被派生记录重写的情况下,需要声明为虚拟方法,一次也不能使用private,因此只能使用protected + 在 C# 10 及更高版本中,即便基记录密封了 ToString 方法,编译器也会在派生的记录中合成 PrintMembers。 还可以创建自己的 PrintMembers 实现。 #### 2.6.5. 派生记录中的解构函数行为 派生记录的 Deconstruct 方法返回编译时类型所有位置属性的值。 如果变量类型为基本记录,则只解构基本记录属性,除非该对象强制转换为派生类型。 下面的示例演示了如何对派生记录调用解构函数 ```csharp public abstract record PersonDeconstruct(string FirstName, string LastName); public record TeacherDeconstruct(string FirstName, string LastName, int Grade) : PersonDeconstruct(FirstName, LastName); public record StudentDeconstruct(string FirstName, string LastName, int Grade) : PersonDeconstruct(FirstName, LastName); public static string Example() { PersonDeconstruct teacher = new TeacherDeconstruct("关羽", "潘凤", 3); var (firstName, lastName) = teacher; // 没有解构Grade属性 Console.WriteLine($"{firstName}, {lastName}");// output: 关羽, 潘凤 var (fName, lName, grade) = (TeacherDeconstruct)teacher; Console.WriteLine($"{fName}, {lName}, {grade}");// output: 关羽, 潘凤, 3 return message; } ``` ### 3. 泛型约束 record 关键字是 class 或 struct 类型的修饰符。 添加 record 修饰符包括本文前面所述的行为。 没有任何泛型约束要求类型是记录。 record class 满足 class 约束。 record struct 满足 struct 约束。 [========] > 参考资料:[《记录(C# 参考)》](https://learn.microsoft.com/zh-cn/dotnet/csharp/language-reference/builtin-types/record#positional-syntax-for-property-definition)、[《实例构造函数(C# 编程指南)》](https://learn.microsoft.com/zh-cn/dotnet/csharp/programming-guide/classes-and-structs/instance-constructors#primary-constructors)
扫一扫手机看
点击排行
windows 文件资源管理器使用
Layui 弹窗open使用说明
SQL Server 中字符串函数使用
C# 使用QQ邮箱代理做邮件接收功能
SQL Server关于apply的两种形式cross apply 和 outer apply
ASP.NET MVC 模型的自定义验证
nvm安装及常用命令
SQL Server 中数据类型转换
SQL Server 中WITH AS短语的使用
IE浏览器下AJAX缓存问题导致数据不更新的解决办法
订阅号