原文連結:https://www.infoworld.com/article/3607372/how-to-work-with-record-types-in-csharp-9.html
原文標題:如何在 C# 9 中使用記錄型別
翻譯:沙漠盡頭的狼(Google 翻譯加持)
利用 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)」。
- 按一下「Next」。
- 在接下來顯示的「Configure your new project」視窗中,指定新專案的名稱和位置。
- 按一下「Create」。
遵循這些步驟將在 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 可以是可變的,但它們主要是為不變性而設計的。