如何在 C# 9 中使用record類型?
原文鏈接:https://www.infoworld.com/article/3607372/how-to-work-with-record-types-in-csharp-9.html
原文標(biāo)題:How to work with record types in C# 9
翻譯:沙漠盡頭的狼(谷歌翻譯加持)

利用 C# 9 中的record類型來構(gòu)建不可變類型和線程安全對象。
不可變性使您的對象線程安全并有助于改進(jìn)內(nèi)存管理。它還使您的代碼更具可讀性和更易于維護(hù)。不可變對象被定義為一旦創(chuàng)建就無法更改的對象。因此,不可變對象本質(zhì)上是線程安全的,并且不受競爭條件的影響。
直到最近,C# 還不支持開箱即用的不可變性。C# 9 通過新的 init-only 屬性和record類型引入了對不可變性的支持。僅init-only屬性可用于使對象的各個屬性不可變,而record可用于使整個對象不可變。
因為不可變對象不會改變它們的狀態(tài),所以在多線程和數(shù)據(jù)傳輸對象等許多用例中,不可變性是一個理想的特性。本文討論了我們?nèi)绾卧?C# 9 中使用 init-only 屬性和record類型。
要使用本文中提供的代碼示例,您應(yīng)該在系統(tǒng)中安裝 Visual Studio 2019。如果您還沒有安裝,可以在此處下載 Visual Studio 2019[1]。
在 Visual Studio 中創(chuàng)建控制臺應(yīng)用程序項目
首先,讓我們在 Visual Studio 中創(chuàng)建一個 .NET Core 控制臺應(yīng)用程序項目。假設(shè)您的系統(tǒng)中安裝了 Visual Studio 2019,請按照下面概述的步驟在 Visual Studio 中創(chuàng)建一個新的 .NET Core 控制臺應(yīng)用程序項目。
啟動 Visual Studio IDE。 單擊“Create new project.”。 在“Create new project”窗口中,從顯示的模板列表中選擇“Console App (.NET Core)”。 點(diǎn)擊下一步。 在接下來顯示的“Configure your new project”窗口中,指定新項目的名稱和位置。 單擊創(chuàng)建。
遵循這些步驟將在 Visual Studio 2019 中創(chuàng)建一個新的 .NET Core 控制臺應(yīng)用程序項目。我們將在本文的后續(xù)部分中使用該項目。
在 C# 9 中使用 init-only 屬性
init-only屬性是那些只能在對象初始化時賦值的屬性。請參閱以下包含 init-only 屬性的類。
public class DbMetadata
{
public string DbName { get; init; }
public string DbType { get; init; }
}
您可以使用以下代碼片段創(chuàng)建 DbMetadata 類的實(shí)例并初始化其屬性。
DbMetadata dbMetadata = new DbMetadata()
{
DbName = "Test",
DbType = "Oracle"
};
請注意,對 init-only 字段的后續(xù)分配是非法的。因此,以下語句將無法編譯。
dbMetadata.DbType = "SQL Server";
在 C# 9 中使用record類型
C# 9 中的record類型是僅具有只讀屬性的輕量級、不可變數(shù)據(jù)類型(或輕量級類)。因為record類型是不可變的,所以它是線程安全的,并且在創(chuàng)建后不能改變或更改。您只能在構(gòu)造函數(shù)中初始化record類型。
您可以使用 record 關(guān)鍵字聲明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; }
}
請注意,僅將類型標(biāo)記為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; }
}
您可以使用以下代碼片段創(chuàng)建 Person 類的實(shí)例并初始化其屬性。
var person = new Person
{
FirstName = "Joydip",
LastName = "Kanjilal",
Address = "192/79 Stafford Hills",
City = "Hyderabad",
Country = "India"
};
在 C# 9 中使用 with 表達(dá)式
如果某些屬性具有相同的值,您可能經(jīng)常希望從另一個對象創(chuàng)建一個對象。但是,記錄類型的 init-only 屬性會阻止這種情況。例如,以下代碼片段將無法編譯,因為默認(rèn)情況下名為 Person 的record類型的所有屬性都是 init-only。
var newPerson = person;
newPerson.Address = "112 Stafford Hills";
newPerson.City = "Bangalore";
幸運(yùn)的是,有一個解決方法——with 關(guān)鍵字。通過指定屬性值的更改,您可以利用 with 關(guān)鍵字從另一個record類型創(chuàng)建一個實(shí)例。以下代碼片段說明了如何實(shí)現(xiàn)這一點(diǎn)。
var newPerson = person with
{ Address = "112 Stafford Hills", City = "Bangalore" };
C# 9 中record類型的繼承
record類型支持繼承。也就是說,您可以從現(xiàn)有record類型創(chuàng)建新record類型并添加新屬性。以下代碼片段說明了如何通過擴(kuò)展現(xiàn)有record類型來創(chuàng)建新record類型。
public record Employee : Person
{
public int Id { get; init; }
public double Salary { get; init; }
}
C# 9 中的位置record
默認(rèn)情況下,使用位置參數(shù)創(chuàng)建的record類型實(shí)例是不可變的。換句話說,您可以通過使用構(gòu)造函數(shù)參數(shù)傳遞有序的參數(shù)列表來創(chuàng)建record類型的不可變實(shí)例,如下面給出的代碼片段所示。
var person = new Person("Joydip", "Kanjilal", "192/79 Stafford Hills", "Hyderabad", "India");
在 C# 9 中檢查record實(shí)例是否相等
在 C# 中檢查類的兩個實(shí)例是否相等時,比較基于這些對象的引用(身份)。但是,如果您檢查record類型的兩個實(shí)例是否相等,則比較基于record類型的實(shí)例中的值。
以下代碼片段說明了一個名為 DbMetadata 的record類型,它由兩個字符串屬性組成。
public record DbMetadata
{
public string DbName { get; init; }
public string DbType { get; init; }
}
以下代碼片段顯示了如何創(chuàng)建 DbMetadata 記錄類型的兩個實(shí)例。
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));
考慮以下創(chuàng)建 DbMetadata record類型的第三個實(shí)例的代碼片段。請注意,實(shí)例 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類型生成以下方法以強(qiáng)制實(shí)施基于值的語義:
Object.Equals(Object)方法的重載接受 record類型作為其參數(shù)的虛擬Equals方法Object.GetHashCode()方法的重載兩個相等運(yùn)算符的方法,即 ==運(yùn)算符 和!=運(yùn)算符record類型實(shí)現(xiàn)System.IEquatable<T>
此外,記錄類型提供了 Object.ToString() 方法的重載。這些方法是隱式生成的,您無需重新實(shí)現(xiàn)它們。
檢查 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);
}
當(dāng)您編譯代碼時,編譯器將用以下消息標(biāo)記錯誤:
Type 'DbMetadata' already defines a member called 'Equals' with the same parameter types
盡管record類型是一個類,但 record 關(guān)鍵字提供了額外的類似值類型的行為和語義,使record與類不同。record本身是一種引用類型,但它使用自己的內(nèi)置相等性檢查——相等性是通過值而不是引用來檢查的。最后,請注意record可以是可變的,但它們主要是為不變性而設(shè)計的。
參考資料
Visual Studio 2019: https://visualstudio.microsoft.com/downloads/
