解構元組及其他類型

Tip

本文屬於 基礎部分, 適合已經至少懂一種程式語言並正在學習 C# 的開發者。 如果你不熟悉模式,可以先從 模式比對概觀 開始。

解構是在單一操作中將值的各個部分——其組成部分——指派給多個變數。 元組的組成部分是其元素,可依位置存取。 另一種類型則是透過定義 Deconstruct 方法來公開元件。 位置記錄類型(positional records),其屬性是以類似建構子的參數宣告的,會自動獲得一個 Deconstruct 方法。

解構元組

假設一個方法回傳一個包含城市資料的元組。 你可以逐一閱讀每個部分:

var cityData = QueryCityData("New York City");
var city = cityData.City;
var population = cityData.Population;
var area = cityData.Area;

拆解會在一個步驟中指派這些元件:

(string city, int population, double area) = QueryCityData("New York City");

你也可以讓 C# 推斷變數類型:

var (city, population, area) = QueryCityData("New York City");

解構可以在一次賦值中混合現有變數、新宣告變數和丟棄變數:

(city, var population, _) = QueryCityData("New York City");

選擇讓程式碼最容易閱讀的格式。 括號前的單一 var 通常是最清晰的推斷形式。 你也可以在括號內混用明確型別和 var,但那種形式通常比較難一眼看清。 如果你只需要部分數值,建議使用捨棄項代替省略位置。

使用捨棄項目忽略不需要的值

每個產生的數值都必須與指派左側的位置對齊。 當你不需要一個或多個位置時,可以使用 _ 作為捨棄項目:

var (_, _, population1960, _, population2010) = QueryPopulationDataForYears(
    "New York City", 1960, 2010);

此處元組回傳城市名稱、兩年及兩個人口值。 分解只保留人口數值,因為計算只使用這些組成項目。

拆解使用者定義型別

類別、結構體或介面可以透過宣告 Deconstruct 方法來支援解構。 每個元件都成為 out 一個參數,讓方法能將值重新指派給呼叫者的變數,而不會回傳該值。 由於每個元件都透過參數 out 回傳,方法本身回傳 void:

public void Deconstruct(out string firstName, out string middleName, out string lastName)
{
    firstName = FirstName;
    middleName = MiddleName;
    lastName = LastName;
}

接著你可以直接拆解一個實例:

var (firstName, middleName, lastName) = passenger;

一個型別可以提供多個 Deconstruct 超載,其 元 數各不相同——即方法宣告的參數數量 out ——因此呼叫者可以選擇要檢索的元件數量:

public void Deconstruct(out string firstName, out string lastName)
{
    firstName = FirstName;
    lastName = LastName;
}

public void Deconstruct(out string firstName, out string middleName, out string lastName)
{
    firstName = FirstName;
    middleName = MiddleName;
    lastName = LastName;
}

public void Deconstruct(out string firstName, out string lastName, out string city, out string state)
{
    firstName = FirstName;
    lastName = LastName;
    city = City;
    state = State;
}

兩個參數數相同的 out 超載則有歧義。 編譯器會對模糊呼叫報告錯誤,因此應依元數區分超載,而非僅依參數類型。

棄置也可以搭配使用者定義的解構函式。 關於棄牌的更多資訊,請參見 棄牌與棄牌模式:

var (firstName, _, city, _) = passenger;

拆解記錄

位置式 record 或 record struct 會在型別宣告本身中將其屬性宣告為參數,類似於建構子。 編譯器會為你產生一個 Deconstruct 方法,具有與這些位置參數相符的 out 參數:

var (city, highTempC, lowTempC) = forecast;

只有位置參數參與所產生的解構作業。 您在紀錄中其他地方宣告的其他屬性不會自動加入。

拆解你不擁有的型別

如果你無法修改某個型別,你仍然可以透過撰寫 一個擴充方法 來支援解構——一種靜態方法,會為你不擁有的型別加入 Deconstruct 方法,就像它是該型別的成員一樣。 加入方法後,任何 Uri 值都可以使用分解語法:

static class UriExtensions
{
    public static void Deconstruct(this Uri uri, out string scheme, out string host, out int port)
    {
        scheme = uri.Scheme;
        host = uri.Host;
        port = uri.Port;
    }
}

同樣的歧義規則也適用於此:兩個具有相同參數個數的擴展 Deconstruct 方法則是歧義的。 實例 Deconstruct 方法與相同參數個數的擴展方法之間也可能產生歧義。 無論哪種情況,編譯器都會針對模糊呼叫回報錯誤。

系統類型的內建解構指定

有些系統類型已經定義了 Deconstruct 方法,其機制與你為自己的類型所使用的相同。 例如,System.Collections.Generic.KeyValuePair<TKey,TValue> 支援解構,讓字典的反覆運算更精簡:

foreach (var (repo, commitCount) in repoCommitCounts)
{
    Console.WriteLine($"{repo} had {commitCount:N0} commits in this snapshot.");
}

解構與模式匹配

一種 Deconstruct 方法也能實現該類型的 位置模式 。 位置模式在一步內測試並拆解一個值,使用與解構相同的括號語法:person is ("Alice", 30) 匹配一個 Person,其解構的組件等於這些值。 這與屬性模式不同,後者直接測試命名屬性,例如 person is { Name: "Alice", Age: 30 }。 物件形狀的屬性模式通常更清楚,因為成員名稱能說明測試。 位置模式在順序本身就帶有意義時最強,例如元組或其他小型有序值。

也請參閱