原文链接:https://www.infoworld.com/article/3607372/how-to-work-with-record-types-in-csharp-9.html
原文标题:How to work with record types in C# 9
翻译:沙漠尽头的狼(谷歌翻译加持)
利用 C# 9 中的record
类型来构建不可变类型和线程安全对象。
不可变性使您的对象线程安全并有助于改进内存管理。它还使您的代码更具可读性和更易于维护。不可变对象被定义为一旦创建就无法更改的对象。因此,不可变对象本质上是线程安全的,并且不受竞争条件的影响。
直到最近,C# 还不支持开箱即用的不可变性。C# 9 通过新的 init-only
属性和record
类型引入了对不可变性的支持。仅init-only
属性可用于使对象的各个属性不可变,而record
可用于使整个对象不可变。
因为不可变对象不会改变它们的状态,所以在多线程和数据传输对象等许多用例中,不可变性是一个理想的特性。本文讨论了我们如何在 C# 9 中使用 init-only
属性和record
类型。
要使用本文中提供的代码示例,您应该在系统中安装 Visual Studio 2019。如果您还没有安装,可以在此处下载 Visual Studio 2019[1]。
在 Visual Studio 中创建控制台应用程序项目
首先,让我们在 Visual Studio 中创建一个 .NET Core 控制台应用程序项目。假设您的系统中安装了 Visual Studio 2019,请按照下面概述的步骤在 Visual Studio 中创建一个新的 .NET Core 控制台应用程序项目。
启动 Visual Studio IDE。
单击“Create new project.”。
在“Create new project”窗口中,从显示的模板列表中选择“Console App (.NET Core)”。
点击下一步。
在接下来显示的“Configure your new project”窗口中,指定新项目的名称和位置。
单击创建。
遵循这些步骤将在 Visual Studio 2019 中创建一个新的 .NET Core 控制台应用程序项目。我们将在本文的后续部分中使用该项目。
在 C# 9 中使用 init-only 属性
init-only
属性是那些只能在对象初始化时赋值的属性。请参阅以下包含 init-only 属性的类。
public class DbMetadata
{public string DbName { get; init; }public string DbType { get; init; }
}
您可以使用以下代码片段创建 DbMetadata 类的实例并初始化其属性。
DbMetadata dbMetadata = new DbMetadata()
{DbName = "Test",DbType = "Oracle"
};
请注意,对 init-only
字段的后续分配是非法的。因此,以下语句将无法编译。
dbMetadata.DbType = "SQL Server";
在 C# 9 中使用record类型
C# 9 中的record
类型是仅具有只读属性的轻量级、不可变数据类型(或轻量级类)。因为record
类型是不可变的,所以它是线程安全的,并且在创建后不能改变或更改。您只能在构造函数中初始化record
类型。
您可以使用 record
关键字声明record
,如下面的代码片段所示。
public record Person
{public string FirstName { get; set; }public string LastName { get; set; }public string Address { get; set; }public string City { get; set; }public string Country { get; set; }
}
请注意,仅将类型标记为record
(如前面的代码片段所示)本身不会为您提供不可变性。要为您的record
类型提供不可变性,您必须使用 init 属性,如下面的代码片段所示。
public record Person
{public string FirstName { get; init; }public string LastName { get; init; }public string Address { get; init; }public string City { get; init; }public string Country { get; init; }
}
您可以使用以下代码片段创建 Person 类的实例并初始化其属性。
var person = new Person
{FirstName = "Joydip",LastName = "Kanjilal",Address = "192/79 Stafford Hills",City = "Hyderabad",Country = "India"
};
在 C# 9 中使用 with 表达式
如果某些属性具有相同的值,您可能经常希望从另一个对象创建一个对象。但是,记录类型的 init-only
属性会阻止这种情况。例如,以下代码片段将无法编译,因为默认情况下名为 Person 的record
类型的所有属性都是 init-only
。
var newPerson = person;
newPerson.Address = "112 Stafford Hills";
newPerson.City = "Bangalore";
幸运的是,有一个解决方法——with 关键字。通过指定属性值的更改,您可以利用 with 关键字从另一个record
类型创建一个实例。以下代码片段说明了如何实现这一点。
var newPerson = person with{ Address = "112 Stafford Hills", City = "Bangalore" };
C# 9 中record类型的继承
record
类型支持继承。也就是说,您可以从现有record
类型创建新record
类型并添加新属性。以下代码片段说明了如何通过扩展现有record
类型来创建新record
类型。
public record Employee : Person
{public int Id { get; init; }public double Salary { get; init; }
}
C# 9 中的位置record
默认情况下,使用位置参数创建的record
类型实例是不可变的。换句话说,您可以通过使用构造函数参数传递有序的参数列表来创建record
类型的不可变实例,如下面给出的代码片段所示。
var person = new Person("Joydip", "Kanjilal", "192/79 Stafford Hills", "Hyderabad", "India");
在 C# 9 中检查record实例是否相等
在 C# 中检查类的两个实例是否相等时,比较基于这些对象的引用(身份)。但是,如果您检查record
类型的两个实例是否相等,则比较基于record
类型的实例中的值。
以下代码片段说明了一个名为 DbMetadata 的record
类型,它由两个字符串属性组成。
public record DbMetadata
{public string DbName { get; init; }public string DbType { get; init; }
}
以下代码片段显示了如何创建 DbMetadata 记录类型的两个实例。
DbMetadata dbMetadata1 = new DbMetadata()
{DbName = "Test",DbType = "Oracle"
};
DbMetadata dbMetadata2 = new DbMetadata()
{DbName = "Test",DbType = "SQL Server"
};
您可以使用 Equals
方法检查相等性。以下两个语句将在控制台窗口中显示“false”。
Console.WriteLine(dbMetadata1.Equals(dbMetadata2));
Console.WriteLine(dbMetadata2.Equals(dbMetadata1));
考虑以下创建 DbMetadata record
类型的第三个实例的代码片段。请注意,实例 dbMetadata1 和 dbMetadata3 包含相同的值。
DbMetadata dbMetadata3 = new DbMetadata()
{DbName = "Test",DbType = "Oracle"
};
以下两条语句将在控制台窗口中显示“true”。
Console.WriteLine(dbMetadata1.Equals(dbMetadata3));
Console.WriteLine(dbMetadata3.Equals(dbMetadata1));
尽管record
类型是引用类型,但 C# 9 提供了合成方法来遵循基于值的相等语义。编译器为您的record
类型生成以下方法以强制实施基于值的语义:
Object.Equals(Object)
方法的重载接受
record
类型作为其参数的虚拟Equals
方法Object.GetHashCode()
方法的重载两个相等运算符的方法,即
==
运算符 和!=
运算符record
类型实现System.IEquatable<T>
此外,记录类型提供了 Object.ToString()
方法的重载。这些方法是隐式生成的,您无需重新实现它们。
检查 C# 中的 Equals 方法
您可以检查是否已隐式生成了 Equals
方法。为此,请在 DbMetadata 记录中添加一个 Equals 方法,如下所示。
public record DbMetadata
{public string DbName { get; init; }public string DbType { get; init; }public override bool Equals(object obj) =>obj is DbMetadata dbMetadata && Equals(dbMetadata);
}
当您编译代码时,编译器将用以下消息标记错误:
Type 'DbMetadata' already defines a member called 'Equals' with the same parameter types
尽管record
类型是一个类,但 record
关键字提供了额外的类似值类型的行为和语义,使record
与类不同。record
本身是一种引用类型,但它使用自己的内置相等性检查——相等性是通过值而不是引用来检查的。最后,请注意record
可以是可变的,但它们主要是为不变性而设计的。
参考资料
[1]
Visual Studio 2019: https://visualstudio.microsoft.com/downloads/