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

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");

コードを最も読みやすくするフォームを選択してください。 括弧の前に単一の var を置く形式が、型推論を用いた最も明確な記述となることが一般的です。 かっこ内に明示的な型と var を混在させることもできますが、その形式は通常、一覧しづらくなります。 一部の値のみが必要な場合は、位置を省略するのではなく、discards を使用してください。

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

生成されるすべての値は、代入式の左辺にある位置と一致している必要があります。 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;

型は、異なる arity (メソッドが宣言する out パラメーターの数) を持つ複数の Deconstruct オーバーロードを提供できるため、呼び出し側は取得するコンポーネントの数を選択できます:

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

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

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 }とは異なります。 プロパティのパターンは、オブジェクトの形状の場合、通常より明確です。これは、メンバー名がそのテストの内容を説明しているためです。 位置パターンは、タプルやその他の小さな順序付けされた値など、順序が既に意味を持つ場合に最も強くなります。

参照