原文链接: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。
在 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可以是可变的,但它们主要是为不变性而设计的。