如何在 C# 9 中使用 record 類型?

如何在 C# 9 中使用 record 類型?

使用過 record 嗎?

最後更新 2021/7/10 下午10:27
沙漠尽头的狼
預計閱讀 8 分鐘
分類
.NET
標籤
.NET C# 記錄

原文連結: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 主控台應用程式專案。

  1. 啟動 Visual Studio IDE。
  2. 按一下「Create new project」。
  3. 在「Create new project」視窗中,從顯示的範本清單中選取「Console App (.NET Core)」。
  4. 按一下「Next」。
  5. 在接下來顯示的「Configure your new project」視窗中,指定新專案的名稱和位置。
  6. 按一下「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 可以是可變的,但它們主要是為不變性而設計的。

繼續探索

延伸閱讀

更多文章
同分類 / 同標籤 2026/2/7

AOT使用經驗總結

從專案建立伊始,就應養成良好的習慣,即只要添加了新功能或使用了較新的語法,就及時進行 AOT 發布測試。

繼續閱讀