值类型记录
在 C# 10 中,我们可以使用 record struct
或 readonly record struct
来声明值类型记录(Record),同时也可以使用 record class
来声明引用类型记录。
# Record Structs
# 基本语法
public record struct Point(int X, int Y);
在上面的例子中,我们定义了一个名为 Point
的 record struct
,它包含两个整型字段 X
和 Y
。在 C# 10 中,我们可以省略 public
关键字,因为默认情况下,所有的记录都是公共的。
# 与 Class 的比较
与类(Class)不同,记录(Record)具有值语义。这意味着当你创建一个新的记录实例时,它将始终是一个全新的实例,而不是指向同一个实例的引用。
var point1 = new Point(10, 20);
var point2 = new Point(10, 20);
Console.WriteLine(point1.Equals(point2)); // 输出 True
Console.WriteLine(point1 == point2); // 输出 True
point1.X = 30;
Console.WriteLine(point1.Equals(point2)); // 输出 False
Console.WriteLine(point1 == point2); // 输出 False
在上面的例子中,我们创建了两个具有相同值的 Point
实例 point1
和 point2
。由于它们的值相同,因此 Equals
方法和相等运算符 ==
都返回 True
。然后,我们修改了 point1
的 X
值,这导致 Equals
方法和相等运算符 ==
返回 False
。
# 继承和接口实现
与类一样,记录也可以继承其他记录或类,并且可以实现接口。
public record struct ColoredPoint(int X, int Y, string Color) : Point(X, Y), IComparable<ColoredPoint>
{
public int CompareTo(ColoredPoint other)
{
if (other is null) return 1;
var result = X.CompareTo(other.X);
if (result == 0)
{
result = Y.CompareTo(other.Y);
}
return result;
}
}
在上面的例子中,我们定义了一个名为 ColoredPoint
的记录,它继承自 Point
记录并实现了 IComparable<ColoredPoint>
接口。
# 只读记录
与类一样,我们也可以使用 readonly
关键字来创建只读记录。
public readonly record struct ReadOnlyPoint(int X, int Y);
只读记录在创建后不能更改其值,它们只能在构造函数中设置。
var point = new ReadOnlyPoint(10, 20);
point.X = 30; // 编译错误:ReadOnlyPoint 中的 X 字段为只读字段
# Record Class
# 基本语法
public record class Person(string FirstName, string LastName);
在上面的例子中,我们定义了一个名为 Person
的 record class
,它包含两个字符串字段 FirstName
和 LastName
。与记录结构不同,记录类具有引用语义。这意味着当你创建一个新的记录类实例时,它将是一个指向同一个实例的引用,而不是全新的实例。
var person1 = new Person("John", "Doe");
var person2 = new Person("John", "Doe");
Console.WriteLine(person1.Equals(person2)); // 输出 True
Console.WriteLine(person1 == person2); // 输出 True
person1.FirstName = "Jane";
Console.WriteLine(person1.Equals(person2)); // 输出 False
Console.WriteLine(person1 == person2); // 输出 False
在上面的例子中,我们创建了两个具有相同值的 Person
实例 person1
和 person2
。由于它们是引用类型,因此 Equals
方法和相等运算符 ==
都返回 True
。然后,我们修改了 person1
的 FirstName
值,这导致 Equals
方法和相等运算符 ==
返回 False
。
# Init-Only 属性
与记录结构一样,记录类也支持 init
访问器,用于在对象创建后设置只读属性的值。这在创建不可变的记录类实例时非常有用。
public record class ImmutablePerson
{
public string FirstName { get; init; }
public string LastName { get; init; }
}
在上面的例子中,我们定义了一个名为 ImmutablePerson
的记录类,它具有两个只读属性 FirstName
和 LastName
,这些属性可以在创建对象后进行初始化。
var person = new ImmutablePerson { FirstName = "John", LastName = "Doe" };
person.FirstName = "Jane"; // 编译错误:ImmutablePerson 中的 FirstName 属性为 init-only 属性
在上面的例子中,我们创建了一个名为 person
的 ImmutablePerson
实例,并设置了 FirstName
和 LastName
的值。由于这些属性是只读的,因此无法在创建后修改它们的值。
# With 表达式
与记录结构一样,记录类也支持 with
表达式,用于创建一个新的记录类实例,并将指定属性的值更改为新值。
var person1 = new Person("John", "Doe");
var person2 = person1 with { FirstName = "Jane" };
在上面的例子中,我们创建了一个名为 person1
的 Person
实例,然后使用 with
表达式创建了一个名为 person2
的新实例,并将 FirstName
的值更改为 Jane
。原始实例 person1
的值保持不变。
# 表达式体成员
与类一样,记录类也支持使用表达式体成员定义方法和属性。这允许我们编写更简洁、更易于阅读和维护的代码。
public record class Person(string FirstName, string LastName)
{
public string FullName => $"{FirstName} {LastName}";
}
```csharp
在上面的例子中,我们使用表达式体成员定义了一个只读属性 `FullName`,它返回 `FirstName` 和 `LastName` 的组合字符串。
### With-Expression 方法
C# 10 引入了新的 `with` 表达式方法,它允许我们使用一个表达式链来更新记录的多个属性。
```csharp
var person1 = new Person("John", "Doe");
var person2 = person1 with { FirstName = "Jane", LastName = "Smith" };
在上面的例子中,我们使用 with
表达式方法一次性更新了 FirstName
和 LastName
的值,从而创建了一个新的 Person
实例 person2
。原始实例 person1
的值保持不变。
# 总结
C# 10 中的记录结构和记录类是非常强大且灵活的语言特性。它们允许我们编写更简洁、更易于阅读和维护的代码,并支持许多常见的场景,如值类型、只读属性、继承和接口实现、表达式体成员以及 with
表达式方法。如果你正在编写 C# 代码,那么记录结构和记录类应该是你工具箱中的必备工具之一。