タプルとその他の型を分解する

Tip

この記事は、少なくとも 1 つのプログラミング言語を既に知っており、C# を学習している開発者向けの 基礎 セクションの一部です。 パターンが初めて使用される場合は、パターン マッチングの概要 から始めます。

分解では、値の個々の部分 (そのコンポーネント) が、1 回の操作で複数の変数に割り当てられます。 タプルのコンポーネントは、位置で識別される要素です。 別の型は、Deconstruct メソッドを定義することでコンポーネントを公開できます。 プロパティをコンストラクターに似たパラメーターとして宣言する位置レコードは、Deconstruct メソッドを自動的に取得します。

タプルの分解

あるメソッドが都市データを含むタプルを返したとします。 各コンポーネントは一度に 1 つずつ読むことができます。

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

分解では、1 ステップでこれらのコンポーネントが割り当てられます。

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

C# に変数の型を推論させることもできます。

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

分解では、既存の変数、新しく宣言された変数、破棄項目を混在させ、1 つの代入で使用することができます。

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

コードを最も読みやすくするフォームを選択してください。 かっこの前の 1 つの var は、多くの場合、最も明確な解釈される形です。 かっこ内に明示的な型と var を混在させることもできますが、その形式は通常、一覧しづらくなります。 一部の値のみが必要な場合は、位置を省略する代わりに破棄変数を使用します。

破棄指定で不要な値を無視する

生成されるすべての値は、代入の左辺の位置に対応している必要があります。 1 つ以上の位置が不要な場合は、捨て値として _ を使用します。

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

ここで、タプルは都市名、2 つの年、および 2 つの人口値を返します。 分解では、計算ではそれらの要素のみを使用するため、母集団の値のみが保持されます。

ユーザー定義型の分解

クラス、構造体、またはインターフェイスは、 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 パラメーターを持つ 2 つのオーバーロードはあいまいです。 コンパイラはあいまいな呼び出しのエラーを報告するため、パラメーター型だけでなく、アリティによってオーバーロードを区別します。

破棄はユーザー定義の分解でも機能します。 破棄全般について詳しくは、破棄と破棄パターンを参照してください。

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

レコードを分解

位置指定レコードrecordまたはrecord structクラスは、コンストラクターと同様に、型宣言自体のパラメーターとしてプロパティを宣言します。 コンパイラによって、これらの位置パラメーターに対応する out パラメーターを持つ Deconstruct メソッドが生成されます。

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;
    }
}

同じあいまいさの規則が適用されます。同じアリティを持つ 2 つの拡張 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メソッドでは、その型の位置指定パターンも有効になります。 位置パターンは、1 つのステップで値をテストして分解します。これは、分解と同じかっこで示された構文を使用します。person is ("Alice", 30) は、分解されたコンポーネントがその値に等しい Person と一致します。 これは、 などの名前付きプロパティを直接テストするperson is { Name: "Alice", Age: 30 }とは異なります。 メンバー名はテストを説明するため、通常、オブジェクトの構造のプロパティ パターンはより明確になります。 位置パターンは、タプルやその他の小さな順序付けされた値など、順序が既に意味を持つ場合に最も強くなります。

参照