C# のタプル型C# Tuple types

C# のタプルは、軽量構文を使用して定義する型で、C# Tuples are types that you define using a lightweight syntax. 構文がシンプルである、変換の規則が要素の数 ("基数" と呼ばれます) と種類に基づく、コピーと割り当ての規則が一貫している、などのメリットがあります。The advantages include a simpler syntax, rules for conversions based on number (referred to as cardinality) and types of elements, and consistent rules for copies and assignments. そのトレードオフとして、タプルでは、継承に関連するオブジェクト指向の表現形式の一部がサポートされていません。As a tradeoff, Tuples do not support some of the object oriented idioms associated with inheritance. 概要については、C# 7 の新機能のタプルに関するトピックのセクションをご覧ください。You can get an overview in the section on Tuples in the What's new in C# 7 topic.

このトピックでは、C# 7 でタプルに適用される言語の規則、タプルの使用方法、およびタプルを操作するための入門的ガイダンスについて説明します。In this topic, you'll learn the language rules governing Tuples in C# 7, different ways to use them, and initial guidance on working with Tuples.

注意

新しいタプル機能を使用するには、ValueTuple 型が必要です。The new tuples features require the ValueTuple types. 型が含まれていないプラットフォームで使用する場合は、NuGet パッケージ System.ValueTuple を追加する必要があります。You must add the NuGet package System.ValueTuple in order to use it on platforms that do not include the types.

これは、フレームワークで提供される型に依存するその他の言語機能に似ています。This is similar to other language features that rely on types delivered in the framework. たとえば、INotifyCompletion インターフェイスに依存する asyncawaitIEnumerable<T> に依存する LINQ などがあります。Examples include async and await relying on the INotifyCompletion interface, and LINQ relying on IEnumerable<T>. ただし、.NET がプラットフォームにさらに依存しなくなりつつあるため、配信メカニズムもそれに応じて変わりつつあります。However, the delivery mechanism is changing as .NET is becoming more platform independent. .NET Framework が、言語コンパイラと同じ周期で配布されるとは限りません。The .NET Framework may not always ship on the same cadence as the language compiler. 新しい言語機能が新しい型に依存する場合、それらの型は、言語機能の配布時に NuGet パッケージとして入手できます。When new language features rely on new types, those types will be available as NuGet packages when the language features ship. これらの新しい型は .NET 標準 API に追加され、フレームワークの一部として配信されるため、NuGet パッケージは必要なくなります。As these new types get added to the .NET Standard API and delivered as part of the framework, the NuGet package requirement will be removed.

詳しく見ていく前に、新しいタプルのサポートを追加した理由について説明します。Let's start with the reasons for adding new Tuple support. メソッドが返すのは 1 つのオブジェクトです。Methods return a single object. タプルを使用すると、その 1 つのオブジェクトに複数の値を簡単にパッケージできます。Tuples enable you to package multiple values in that single object more easily.

.NET Framework には Tuple ジェネリック クラスが既にありますが、The .NET Framework already has generic Tuple classes. このクラスには 2 つの大きな制限がありました。These classes, however, had two major limitations. 1 つは、Tuple クラスのプロパティに Item1Item2 という名前が付けられるというものです。For one, the Tuple classes named their properties Item1, Item2, and so on. その名前にはセマンティック情報が保持されていません。Those names carry no semantic information. つまり、この Tuple 型を使用しても、各プロパティの情報を伝達することはできません。Using these Tuple types does not enable communicating the meaning of each of the properties. 新しい言語機能を使用すると、タプルの要素に意味的にわかりやすい名前を宣言して使用できます。The new language features enable you to declare and use semantically meaningful names for the elements in a tuple.

さらに、Tuple クラスが参照型であるのも、懸念事項の 1 つです。Another concern is that the Tuple classes are reference types. Tuple 型を使用するには、オブジェクトを割り当てる必要があります。Using one of the Tuple types means allocating objects. ホット パスでは、これがアプリケーションのパフォーマンスに大きな影響を及ぼすことがあります。On hot paths, this can have a measurable impact on your application's performance. そのため、タプルの言語サポートでは、新しい ValueTuple 構造体を活用します。Therefore, the language support for tuples leverages the new ValueTuple structs.

classstruct を作成して複数の要素を伝達すれば、この弱点を回避できますが、To avoid those deficiencies, you could create a class or a struct to carry multiple elements. その分、手間が増え、設計の意図がわかりにくくなります。Unfortunately, that's more work for you, and it obscures your design intent. structclass を作成すると、データと動作の両方で型を定義しなければなりませんが、Making a struct or class implies that you are defining a type with both data and behavior. 1 つのオブジェクトに複数の値を格納したいだけという状況もよくあります。Many times, you simply want to store multiple values in a single object.

これらの機能と ValueTuple ジェネリック構造体では、タプル型に動作 (メソッド) を追加できないという規則が適用されます。The language features and the ValueTuple generic structs enforce the rule that you cannot add any behavior (methods) to these tuple types. すべての ValueTuple 型が "mutable 構造体" で、All the ValueTuple types are mutable structs. 各メンバー フィールドがパブリック フィールドであるため、Each member field is a public field. 非常に軽量です。That makes them very lightweight. ただし、不変性が重要な場合は、タプルを使用しないでください。However, that means tuples should not be used where immutability is important.

タプルは、class 型や struct 型に比べて、シンプルで柔軟なデータ コンテナーです。Tuples are both simpler and more flexible data containers than class and struct types. 両者の違いを見てみましょう。Let's explore those differences.

名前付きのタプルと名前がないタプルNamed and unnamed tuples

既存の Tuple 型で定義されたプロパティと同様、ValueTuple 構造体のフィールドには Item1Item2Item3 といった名前が付いています。The ValueTuple struct has fields named Item1, Item2, Item3 and so on, similar to the properties defined in the existing Tuple types. "名前のないタプル" には、この名前しか使用できません。These names are the only names you can use for unnamed tuples. タプルに代替フィールド名を付けなかった場合は、名前のないタプルが作成されます。When you do not provide any alternative field names to a tuple, you've created an unnamed tuple:

var unnamed = ("one", "two");

前の例のタプルは、リテラル定数を使って初期化されており、C# 7.1 のタプル フィールド名プロジェクションを使って作成された要素名はありません。The tuple in the previous example was initialized using literal constants and won't have element names created using Tuple field name projections in C# 7.1.

タプルを初期化する際に、新しい機能を使用して、各フィールドにわかりやすい名前を付けることができます。However, when you initialize a tuple, you can use new language features that give better names to each field. これによって、"名前付きタプル" が作成されます。Doing so creates a named tuple. 名前付きタプルには Item1Item2Item3 という名前の要素がまだ存在しますが、Named tuples still have elements named Item1, Item2, Item3 and so on. 名前を付けた要素に対してシノニムも設定されます。But they also have synonyms for any of those elements that you have named. 名前付きタプルを作成するには、各要素の名前を指定します。You create a named tuple by specifying the names for each element. たとえば、タプル初期化の一環として名前を指定できます。One way is to specify the names as part of the tuple initialization:

var named = (first: "one", second: "two");

コンパイラと言語によってシノニムが処理されるため、名前付きタプルを効果的に使用できるようになります。These synonyms are handled by the compiler and the language so that you can use named tuples effectively. IDE やエディターは Roslyn API を使用して、セマンティック名を読み取ります。IDEs and editors can read these semantic names using the Roslyn APIs. これにより、同じアセンブリ内の任意の場所で、セマンティック名によって名前付きタプルの要素を参照できます。This enables you to reference the elements of a named tuple by those semantic names anywhere in the same assembly. 定義した名前は、コンパイル済み出力が生成されるときに、対応する Item* に置き換えられます。The compiler replaces the names you've defined with Item* equivalents when generating the compiled output. これらの要素に設定した名前は、コンパイルされた Microsoft Intermediate Language (MSIL) には含まれません。The compiled Microsoft Intermediate Language (MSIL) does not include the names you've given these elements.

C# 7.1 以降、タプルのフィールド名は、タプルの初期化に使用した変数によって指定される場合があります。Beginning with C# 7.1, the field names for a tuple may be provided from the variables used to initialize the tuple. これは、タプル プロジェクション初期化子と呼ばれます。This is referred to as tuple projection initializers. 次のコードでは、要素 count (整数)、および sum (倍精度浮動小数点型) で accumulation という名前のタプルを作成します。The following code creates a tuple named accumulation with elements count (an integer), and sum (a double).

var sum = 12.5;
var count = 5;
var accumulation = (count, sum);

コンパイラは、パブリック メソッドまたはプロパティから返されたタプルに指定されている名前を伝える必要があります。The compiler must communicate those names you created for tuples that are returned from public methods or properties. このような場合、コンパイラはメソッドに TupleElementNamesAttribute 属性を追加します。In those cases, the compiler adds a TupleElementNamesAttribute attribute on the method. この属性には、タプルの各要素に付けられた名前が含まれた TransformNames リスト プロパティが含まれています。This attribute contains a TransformNames list property that contains the names given to each of the elements in the Tuple.

注意

Visual Studio などの開発ツールも、そのメタデータを読み取り、メタデータ フィールド名を使用する IntelliSense などの機能を提供します。Development Tools, such as Visual Studio, also read that metadata, and provide IntelliSense and other features using the metadata field names.

名前付きタプルを相互に割り当てるための規則を理解するには、新しいタプルと ValueTuple 型の基本を理解しておくことが重要です。It is important to understand these underlying fundamentals of the new tuples and the ValueTuple type in order to understand the rules for assigning named tuples to each other.

タプル プロジェクション初期化子Tuple projection initializers

一般に、タプル プロジェクション初期化子は、タプルの初期化ステートメントの右側にある変数またはフィールド名を使用して機能します。In general, tuple projection initializers work by using the variable or field names from the right-hand side of a tuple initialization statement. 明示的な名前が指定された場合は、射影された名前より優先されます。If an explicit name is given, that takes precedence over any projected name. たとえば、次の初期化子では、要素は localVariableOnelocalVariableTwo ではなく、explicitFieldOneexplicitFieldTwo になります。For example, in the following initializer, the elements are explicitFieldOne and explicitFieldTwo, not localVariableOne and localVariableTwo:

var localVariableOne = 5;
var localVariableTwo = "some text";

var tuple = (explicitFieldOne: localVariableOne, explicitFieldTwo: localVariableTwo);

明示的な名前が指定されていないフィールドの場合、適用可能な暗黙的な名前が射影されます。For any field where an explicit name is not provided, an applicable implicit name will be projected. 明示的または暗黙的のいずれかで、セマンティック名を指定するための要件はありません。Note that there is no requirement to provide semantic names, either explicitly or implicitly. 次の初期化子はフィールド名がItem1、値がある42StringContent値が「すべての応答」。The following initializer will have field names Item1, whose value is 42 and StringContent, whose value is "The answer to everything":

var stringContent = "The answer to everything";
var mixedTuple = (42, stringContent);

候補フィールド名がタプル フィールドに射影されない場合の条件は 2 つあります。There are two conditions where candidate field names are not projected onto the tuple field:

  1. 候補名が予約されているタプル名の場合。When the candidate name is a reserved tuple name. 例としては、Item3ToString または Rest です。Examples include Item3, ToString or Rest.
  2. 候補名が、別のタプル フィールド名 (明示的または暗黙的のいずれか) の複製である場合。When the candidate name is a duplicate of another tuple field name, either explicit or implicit.

これらの条件によってあいまいさを回避します。These conditions avoid ambiguity. この名前がタプルのフィールドのフィールド名として使用される場合、あいまいさの原因となります。These names would cause an ambiguity if they were used as the field names for a field in a tuple. この条件はどちらも、コンパイル時エラーを発生させることはありません。Neither of these conditions cause compile time errors. 代わりに、射影された名前のない要素には、射影されたセマンティック名がありません。Instead, the elements without projected names do not have semantic names projected for them. これらの条件の例を以下に示します。The following examples demonstrate these conditions:

var ToString = "This is some text";
var one = 1;
var Item1 = 5;
var projections = (ToString, one, Item1);
// Accessing the first field:
Console.WriteLine(projections.Item1);
// There is no semantic name 'ToString'
// Accessing the second field:
Console.WriteLine(projections.one);
Console.WriteLine(projections.Item2);
// Accessing the third field:
Console.WriteLine(projections.Item3);
// There is no semantic name 'Item`.

var pt1 = (X: 3, Y: 0);
var pt2 = (X: 3, Y: 4);

var xCoords = (pt1.X, pt2.X);
// There are no semantic names for the fields
// of xCoords. 

// Accessing the first field:
Console.WriteLine(xCoords.Item1);
// Accessing the second field:
Console.WriteLine(xCoords.Item2);

これらの条件は、タプル フィールド名プロジェクションが利用できなかった場合、C# 7.0 で記述されたコードに対する重大な変更になるため、コンパイラ エラーが発生することはありません。These situations do not cause compiler errors because that would be a breaking change for code written with C# 7.0, when tuple field name projections were not available.

割り当てとタプルAssignment and tuples

要素の数が同じで、これらの各要素に型の暗黙的な変換があるタプル型間での、代入がサポートされています。The language supports assignment between tuple types that have the same number of elements and implicit conversions for the types for each of those elements. 他の変換は、割り当てでは考慮されません。Other conversions are not considered for assignments. タプル型間で許可されている割り当ての種類を見てみましょう。Let's look at the kinds of assignments that are allowed between tuple types.

以降の例で使用されている変数について考えます。Consider these variables used in the following examples:

// The 'arity' and 'shape' of all these tuples are compatible. 
// The only difference is the field names being used.
var unnamed = (42, "The meaning of life");
var anonymous = (16, "a perfect square");
var named = (Answer: 42, Message: "The meaning of life");
var differentNamed = (SecretConstant: 42, Label: "The meaning of life");

最初の 2 つの変数 unnamed および anonymous では、要素にセマンティック名が割り当てられていません。The first two variables, unnamed and anonymous do not have semantic names provided for the elements. フィールド名は Item1Item2 になります。The field names are Item1 and Item2. 最後の 2 つの変数 named および differentName では、要素にセマンティック名が付けられています。The last two variables, named and differentName have semantic names given for the elements. この 2 つのタプルでは、要素名が異なっていることに注意してください。Note that these two tuples have different names for the elements.

この 4 つのタプルに含まれている要素の数 ("基数" と呼ばれます) と要素の型は同じです。All four of these tuples have the same number of elements (referred to as 'cardinality') and the types of those elements are identical. このため、これらの割り当てはすべて機能します。Therefore, all of these assignments work:

unnamed = named;

named = unnamed;
// 'named' still has fields that can be referred to
// as 'answer', and 'message':
Console.WriteLine($"{named.Answer}, {named.Message}");

// unnamed to unnamed:
anonymous = unnamed;

// named tuples.
named = differentNamed;
// The field names are not assigned. 'named' still has 
// fields that can be referred to as 'answer' and 'message':
Console.WriteLine($"{named.Answer}, {named.Message}");

// With implicit conversions:
// int can be implicitly converted to long
(long, string) conversion = named;

タプルの名前が割り当てられていないことに注意してください。Notice that the names of the tuples are not assigned. 要素の値は、タプルの要素の順序に従って割り当てられます。The values of the elements are assigned following the order of the elements in the tuple.

要素の型または数が異なるタプルを割り当てることはできません。Tuples of different types or numbers of elements are not assignable:

// Does not compile.
// CS0029: Cannot assign Tuple(int,int,int) to Tuple(int, string)
var differentShape = (1, 2, 3);
named = differentShape;

メソッドの戻り値としてのタプルTuples as method return values

タプルはメソッドの戻り値として使用できます。これはタプルの一般的な使用方法の 1 つです。One of the most common uses for Tuples is as a method return value. その例を見てみましょう。Let's walk through one example. 数値シーケンスの標準偏差を計算する次のメソッドについて考えます。Consider this method that computes the standard deviation for a sequence of numbers:

public static double StandardDeviation(IEnumerable<double> sequence)
{
    // Step 1: Compute the Mean:
    var mean = sequence.Average();

    // Step 2: Compute the square of the differences between each number 
    // and the mean:
    var squaredMeanDifferences = from n in sequence
                                 select (n - mean) * (n - mean);
    // Step 3: Find the mean of those squared differences:
    var meanOfSquaredDifferences = squaredMeanDifferences.Average();

    // Step 4: Standard Deviation is the square root of that mean:
    var standardDeviation = Math.Sqrt(meanOfSquaredDifferences);
    return standardDeviation;
}

注意

この例では、未修正のサンプル標準偏差を計算します。These examples compute the uncorrected sample standard deviation. 修正後のサンプル標準偏差式は、Average 拡張メソッドで行われるのと同様に、平均値との差の二乗和を、N ではなく (N-1) で除算します。The corrected sample standard deviation formula would divide the sum of the squared differences from the mean by (N-1) instead of N, as the Average extension method does. 標準偏差のこうした数式の間に生じる差の詳細については、統計値のテキストを参照してください。Consult a statistics text for more details on the differences between these formulas for standard deviation.

これは、標準偏差の教科書どおりの数式に従っています。This follows the textbook formula for the standard deviation. 正しい答えが生成されますが、きわめて非効率的な実装です。It produces the correct answer, but it's a very inefficient implementation. このメソッドは、シーケンスを 2 回列挙します。1 回は平均値を生成するため、もう 1 回は平均値との差を 2 乗して、その平均値を生成するためです This method enumerates the sequence twice: Once to produce the average, and once to produce the average of the square of the difference of the average. (前述のとおり、LINQ クエリは遅延評価されるため、平均値との差と、その差の平均値の計算で生成される列挙は 1 つだけです)。(Remember that LINQ queries are evaluated lazily, so the computation of the differences from the mean and the average of those differences makes only one enumeration.)

シーケンスの列挙を 1 つだけ使用して標準偏差を計算する、別の数式があります。There is an alternative formula that computes standard deviation using only one enumeration of the sequence. この計算では、シーケンスを列挙しながら、2 つの値が生成されます。1 つはシーケンス内のすべての項目の合計、もう 1 つは各値の二乗和です。This computation produces two values as it enumerates the sequence: the sum of all items in the sequence, and the sum of the each value squared:

public static double StandardDeviation(IEnumerable<double> sequence)
{
    double sum = 0;
    double sumOfSquares = 0;
    double count = 0;

    foreach (var item in sequence)
    {
        count++;
        sum += item;
        sumOfSquares += item * item;
    }

    var variance = sumOfSquares - sum * sum / count;
    return Math.Sqrt(variance / count);
}

このバージョンでは、シーケンスを 1 回だけ列挙しますが、This version enumerates the sequence exactly once. 最適な再利用可能なコードとは言えません。But, it's not very reusable code. 操作を続けていくと、さまざまな統計計算処理の多くが、シーケンス内の項目数、シーケンスの合計、およびシーケンスの二乗和を使用していることがわかります。As you keep working, you'll find that many different statistical computations use the number of items in the sequence, the sum of the sequence, and the sum of the squares of the sequence. このメソッドをリファクタリングし、その 3 つの値すべてを生成するユーティリティ メソッドを作成しましょう。Let's refactor this method and write a utility method that produces all three of those values.

ここで、タプルが非常に役に立ちます。This is where tuples come in very useful.

このメソッドを更新して、列挙中に計算された 3 つの値をタプルに格納しましょう。Let's update this method so the three values computed during the enumeration are stored in a tuple. そうすると、次のバージョンが作成されます。That creates this version:

public static double StandardDeviation(IEnumerable<double> sequence)
{
    var computation = (Count: 0, Sum: 0.0, SumOfSquares: 0.0);

    foreach (var item in sequence)
    {
        computation.Count++;
        computation.Sum += item;
        computation.SumOfSquares += item * item;
    }

    var variance = computation.SumOfSquares - computation.Sum * computation.Sum / computation.Count;
    return Math.Sqrt(variance / computation.Count);
}

Visual Studio のリファクタリングのサポートにより、簡単にプライベート メソッドには、コアの統計情報の機能を抽出します。Visual Studio's Refactoring support makes it easy to extract the functionality for the core statistics into a private method. これにより、3 つの値 SumSumOfSquaresCount を含むタプル型を返す private static メソッドが作成されます。That gives you a private static method that returns the tuple type with the three values of Sum, SumOfSquares, and Count:

public static double StandardDeviation(IEnumerable<double> sequence)
{
    (int Count, double Sum, double SumOfSquares) computation = ComputeSumsAnSumOfSquares(sequence);

    var variance = computation.SumOfSquares - computation.Sum * computation.Sum / computation.Count;
    return Math.Sqrt(variance / computation.Count);
}

private static (int Count, double Sum, double SumOfSquares) ComputeSumsAnSumOfSquares(IEnumerable<double> sequence)
{
    var computation = (count: 0, sum: 0.0, sumOfSquares: 0.0);

    foreach (var item in sequence)
    {
        computation.count++;
        computation.sum += item;
        computation.sumOfSquares += item * item;
    }

    return computation;
}

編集を手動ですばやく行う必要がある場合は、使用できるオプションが他にもいくつかあります。The language enables a couple more options that you can use, if you want to make a few quick edits by hand. まず、var 宣言を使用することで、ComputeSumAndSumOfSquares メソッド呼び出しのタプルの結果を初期化できます。First, you can use the var declaration to initialize the tuple result from the ComputeSumAndSumOfSquares method call. ComputeSumAndSumOfSquares メソッド内に異なる 3 つの変数を作成することもできます。You can also create three discrete variables inside the ComputeSumAndSumOfSquares method. 最終的なバージョンは以下のようになります。The final version is below:

public static double StandardDeviation(IEnumerable<double> sequence)
{
    var computation = ComputeSumAndSumOfSquares(sequence);

    var variance = computation.SumOfSquares - computation.Sum * computation.Sum / computation.Count;
    return Math.Sqrt(variance / computation.Count);
}

private static (int Count, double Sum, double SumOfSquares) ComputeSumAndSumOfSquares(IEnumerable<double> sequence)
{
    double sum = 0;
    double sumOfSquares = 0;
    int count = 0;

    foreach (var item in sequence)
    {
        count++;
        sum += item;
        sumOfSquares += item * item;
    }

    return (count, sum, sumOfSquares);
}

この最終バージョンは、この 3 つの値を必要とするすべてのメソッド、またはそのサブセットで使用できます。This final version can be used for any method that needs those three values, or any subset of them.

このようなタプルを返すメソッドで要素の名前を管理するオプションが他にもサポートされています。The language supports other options in managing the names of the elements in these tuple-returning methods.

戻り値の宣言からフィールド名を削除して、名前のないタプルを返すことができます。You can remove the field names from the return value declaration and return an unnamed tuple:

private static (double, double, int) ComputeSumAndSumOfSquares(IEnumerable<double> sequence)
{
    double sum = 0;
    double sumOfSquares = 0;
    int count = 0;

    foreach (var item in sequence)
    {
        count++;
        sum += item;
        sumOfSquares += item * item;
    }

    return (sum, sumOfSquares, count);
}

このタプルのフィールドは、Item1Item2Item3 として扱う必要があります。You must address the fields of this tuple as Item1, Item2, and Item3. メソッドから返されたタプルの要素には、セマンティック名を指定することをお勧めします。It's recommended that you provide semantic names to the elements of tuples returned from methods.

また、作成している LINQ クエリの最終的な結果が、選択したオブジェクトのプロパティが一部だけ含まれるプロジェクションとなるときも、タプルが非常に便利です。Another idiom where tuples can be very useful is when you are authoring LINQ queries where the final result is a projection that contains some, but not all, of the properties of the objects being selected.

従来、クエリの結果は、匿名型のオブジェクトのシーケンスに射影していましたが、You would traditionally project the results of the query into a sequence of objects that were an anonymous type. この方法には多くの制限が伴いました。メソッドの戻り値の型では、匿名型に名前を付けるのが簡単ではなかったからです。That presented many limitations, primarily because anonymous types could not conveniently be named in the return type for a method. 代替手段として objectdynamic を結果の型に使用すると、パフォーマンス コストは大きくなります。Alternatives using object or dynamic as the type of the result came with significant performance costs.

タプル型のシーケンスを返すのは簡単です。要素の名前と型は、コンパイル時に IDE ツールで使用することができます。Returning a sequence of a tuple type is easy, and the names and types of the elements are available at compile time and through IDE tools. たとえば、次の ToDo アプリケーションを考えてみます。For example, consider a ToDo application. ToDo リストの 1 つのエントリを表すために、次のようなクラスを定義します。You might define a class similar to the following to represent a single entry in the ToDo list:

public class ToDoItem
{
    public int ID { get; set; }
    public bool IsDone { get; set; }
    public DateTime DueDate { get; set; }
    public string Title { get; set; }
    public string Notes { get; set; }    
}

モバイル アプリケーションでサポートされるのは、タイトルしか表示されないコンパクト形式の現在の ToDo 項目です。Your mobile applications may support a compact form of the current ToDo items that only displays the title. その LINQ クエリでは、ID とタイトルのみが含まれるプロジェクションが作成されます。That LINQ query would make a projection that includes only the ID and the title. タプルのシーケンスを返すメソッドは、その設計を適切に表現しています。A method that returns a sequence of tuples expresses that design very well:

internal IEnumerable<(int ID, string Title)> GetCurrentItemsMobileList()
{
    return from item in AllItems
           where !item.IsDone
           orderby item.DueDate
           select (item.ID, item.Title);
}

注意

C# 7.1 では、タプル プロジェクションを使用して、匿名型で名前が付けられたプロパティと同様の方法で、要素を使用する名前付きタプルを作成できます。In C# 7.1, tuple projections enable you to create named tuples using elements, in a manner similar to the property naming in anonymous types. 上記のコードでは、クエリ プロジェクションの select ステートメントで、要素 IDTitle を含むタプルを作成します。In the above code, the select statement in the query projection creates a tuple that has elements ID and Title.

名前付きタプルは、署名に含めることができます。The named tuple can be part of the signature. これによって、コンパイラと IDE ツールは静的チェックを行い、結果が正しく使用されていることを確認できます。It lets the compiler and IDE tools provide static checking that you are using the result correctly. 名前付きタプルには静的情報も含まれているため、リフレクション、動的バインドなど、コストのかかるランタイム機能を使用して結果を操作する必要がありません。The named tuple also carries the static type information so there is no need to use expensive run time features like reflection or dynamic binding to work with the results.

分解Deconstruction

タプル内のすべての項目を展開するには、メソッドによって返されるタプルを分解します。You can unpackage all the items in a tuple by deconstructing the tuple returned by a method. 組を分解する 3 つの異なるアプローチがあります。There are three different approaches to deconstructing tuples. まず、かっこの中で各フィールドの型を明示的に宣言して、タプルの要素ごとに個別の変数を作成することができます。First, you can explicitly declare the type of each field inside parentheses to create discrete variables for each of the elements in the tuple:

public static double StandardDeviation(IEnumerable<double> sequence)
{
    (int count, double sum, double sumOfSquares) = ComputeSumAndSumOfSquares(sequence);

    var variance = sumOfSquares - sum * sum / count;
    return Math.Sqrt(variance / count);
}

また、かっこの外に var キーワードを使用して、タプルの各フィールドに対して暗黙的に型指定された変数を宣言することもできます。You can also declare implicitly typed variables for each field in a tuple by using the var keyword outside the parentheses:

public static double StandardDeviation(IEnumerable<double> sequence)
{
    var (sum, sumOfSquares, count) = ComputeSumAndSumOfSquares(sequence);

    var variance = sumOfSquares - sum * sum / count;
    return Math.Sqrt(variance / count);
}

var キーワードは、かっこ内のいずれか 1 つの変数宣言に使用することも、すべての変数宣言に使用することもできます。It is also legal to use the var keyword with any, or all of the variable declarations inside the parentheses.

(double sum, var sumOfSquares, var count) = ComputeSumAndSumOfSquares(sequence);

タプル内のフィールドすべての型が同じでも、かっこ外では使用できない型があることに注意してください。Note that you cannot use a specific type outside the parentheses, even if every field in the tuple has the same type.

同様に、既存の宣言との組を分解できます。You can deconstruct tuples with existing declarations as well:

public class Point
{
    public int X { get; set; }
    public int Y { get; set; }

    public Point(int x, int y) => (X, Y) = (x, y);
}

警告

既存の宣言に、かっこ内の宣言を混在させることはできません。You cannot mix existing declarations with declarations inside the parentheses. たとえば、次が許可されていません:(var x, y) = MyMethod();です。For instance, the following is not allowed: (var x, y) = MyMethod();. これにより作成されるエラー CS8184 のためxかっこが宣言されているとyが以前に宣言された他の場所。This produces error CS8184 because x is declared inside the parentheses and y is previously declared elsewhere.

ユーザー定義型の分解Deconstructing user defined types

上に示したように、すべてのタプル型を分解できます。Any tuple type can be deconstructed as shown above. また、ユーザー定義型 (クラス、構造体、またはインターフェイス) も簡単に分解できます。It's also easy to enable deconstruction on any user defined type (classes, structs, or even interfaces).

型の作成者は、型を構成するデータ要素を表す任意の数の out 変数に対して値を割り当てる Deconstruct メソッドを 1 つ以上定義できます。The type author can define one or more Deconstruct methods that assign values to any number of out variables representing the data elements that make up the type. たとえば、次の Person 型は、person オブジェクトを、名と姓を表す要素に分解する Deconstruct メソッドを定義しています。For example, the following Person type defines a Deconstruct method that deconstructs a person object into the elements representing the first name and last name:

public class Person
{
    public string FirstName { get; }
    public string LastName { get; }

    public Person(string first, string last)
    {
        FirstName = first;
        LastName = last;
    }

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

deconstruct メソッドを使用すると、Person から、FirstName プロパティと LastName プロパティを表す 2 つの文字列を割り当てることができます。The deconstruct method enables assignment from a Person to two strings, representing the FirstName and LastName properties:

var p = new Person("Althea", "Goodwin");
var (first, last) = p;

自分で作成していない型を分解することもできます。You can enable deconstruction even for types you did not author. Deconstruct メソッドは、オブジェクトのアクセス可能なデータ メンバーを展開する拡張メソッドとして使用できます。The Deconstruct method can be an extension method that unpackages the accessible data members of an object. 次の例は、Person から派生した Student 型と、Student を 3 つの変数 FirstNameLastNameGPA に分解する拡張メソッドを示しています。The example below shows a Student type, derived from the Person type, and an extension method that deconstructs a Student into three variables, representing the FirstName, the LastName and the GPA:

public class Student : Person
{
    public double GPA { get; }
    public Student(string first, string last, double gpa) :
        base(first, last)
    {
        GPA = gpa;
    }
}

public static class Extensions
{
    public static void Deconstruct(this Student s, out string first, out string last, out double gpa)
    {
        first = s.FirstName;
        last = s.LastName;
        gpa = s.GPA;
    }
}

Student オブジェクトには、アクセス可能な Deconstruct メソッドが 2 つあります。Student 型に対して宣言された拡張メソッドと、Person 型のメンバーです。A Student object now has two accessible Deconstruct methods: the extension method declared for Student types, and the member of the Person type. 両方ともスコープ内にあり、Student を 2 つまたは 3 つの変数に分解できます。Both are in scope, and that enables a Student to be deconstructed into either two variables or three. student を 3 つの変数に割り当てると、名、姓、GPA のすべてが返されます。If you assign a student to three variables, the first name, last name, and GPA are all returned. student を 2 つの変数に割り当てると、名と姓のみが返されます。If you assign a student to two variables, only the first name and the last name are returned.

var s1 = new Student("Cary", "Totten", 4.5);
var (fName, lName, gpa) = s1;

クラスまたはクラス階層で複数の Deconstruct メソッドを定義するときには注意が必要です。You should be very careful defining multiple Deconstruct methods in a class or a class hierarchy. out パラメーターの数が同じ Deconstruct メソッドが複数あると、あいまいさが生じ、Multiple Deconstruct methods that have the same number of out parameters can quickly cause ambiguities. 呼び出し元が、必要な Deconstruct メソッドを簡単には呼び出せなくなる場合があります。Callers may not be able to easily call the desired Deconstruct method.

この例では、出力パラメーターが PersonDeconstruct メソッドには 2 つ、StudentDeconstruct メソッドには 3 つ含まれるため、呼び出しが不明確になる可能性は最小限に抑えられています。In this example, there is minimal chance for an ambiguous call because the Deconstruct method for Person has two output parameters, and the Deconstruct method for Student has three.

まとめConclusion

クラスや構造体では動作の定義が必要であるため、新しい言語とライブラリで名前付きタプルがサポートされたことで、動作を定義せずに複数の要素を格納するデータ構造設計が格段に扱いやすくなりました。The new language and library support for named tuples makes it much easier to work with designs that use data structures that store multiple elements but do not define behavior, as classes and structs do. こうした型に対してタプルを使用するのは簡単です。It's easy and concise to use tuples for those types. 詳細な class または struct 構文を使用して型を作成しなくても、静的な型チェックのすべてのメリットを利用できます。You get all the benefits of static type checking, without needing to author types using the more verbose class or struct syntax. とは言っても、class や struct は、privateinternal のユーティリティ メソッドにとっては非常に便利です。Even so, they are most useful for utility methods that are private, or internal. 複数の要素を含む値がパブリック メソッドによって返される場合は、ユーザー定義型、class またはstruct を作成します。Create user defined types, either class or struct types when your public methods return a value that has multiple elements.