C# 7.0 から使用できるようになった値型のタプルです。
実体はSystem.ValueTupleです。C# 4から使えたSystem.Tupleがありましたが似て異なるものです。ここではSystem.ValueTupleについて扱います。
初期化と参照
(double, int) t1 = (7.2, 3);
Console.WriteLine($"item: {t1.Item1} item2: {t1.Item2}");
// item: 7.2 item2: 3
(double Sum, int Count) t2 = (7.2, 3);
Console.WriteLine($"Count: {t2.Count} Sum: {t2.Sum}");
// Count: 3 Sum: 7.2
必要に応じて項目名(フィールド名)が使用できます。
初期化に使用した変数名から項目名が推測されるようになりました(C#7.1以降)。
var sum = 4.5;
var count = 3;
var t = (count, sum);
Console.WriteLine($"count: {t.Item1} sum: {t.Item2}");
Console.WriteLine($"count: {t.count} sum: {t.sum}");
// count: 3 sum: 4.5
項目名の使用は任意ですが、項目名を明示的に指定しなかった場合には、既定名としてItem1、Item2、Item3…で要素を参照できます。明示的に指定した項目名と既定名は問題なく併用することができます。
ValueTupleはミュータブル
tuple の要素は書き換え可能(ミュータブル)です。また、tuple自身も書き換え可能です。
var t = (x: 1, y: 2);
t.x = 10;
t.y = 20;
Console.WriteLine($"x:{t.x} y:{t.y}");
// x:10 y:20
// タプル自身も書き換え可能
t = (100, 200);
Console.WriteLine($"x:{t.x} y:{t.y}");
// x:100 y:200
ユースケース ~tupleの使いどころ
tupleの使いどころの代表例はメソッドの戻り値です。例えばout パラメータをリファクタリングして tuple を返すようにすると使いやすくなります。
次の例はmin, maxを求めるFindMinMaxメソッドの結果を、2つのoutパラメータで返しています。
int[] xs = new int[] { -9, 0, 67, 100 };
FindMinMax(xs, out int min, out int max);
Console.WriteLine($"min: {min} and max:{max}");
// min: -9 and max:100
void FindMinMax(int[] input, out int min, out int max)
{
min = int.MaxValue;
max = int.MinValue;
foreach (var i in input)
{
if (i < min) min = i;
if (i > max) max = i;
}
}
FindMinMaxメソッドの戻り値をtuple型として書き換えたのが下の例です。
int[] xs = new int[] { -9, 0, 67, 100 };
var limits = FindMinMax(xs);
Console.WriteLine($"min: {limits.min} and max:{limits.max}");
// min: -9 and max:100
// 戻り値をtupleで書き換え
(int min, int max) FindMinMax(int[] input)
{
var min = int.MaxValue;
var max = int.MinValue;
foreach (var i in input)
{
if (i < min) min = i;
if (i > max) max = i;
}
return (min, max);
}
代入と分解
二つのTuple型間で、二つの条件を両方満たせば代入が可能です。
- 要素数が同じ
- 対応する要素の型が同じもしくは暗黙に変換可能
(int, string) t1 = (19, "20");
(int First, string Second) t2;
t2 = t1;
Console.WriteLine($"t2: {t2.First} and {t2.Second}");
(double First, string Second) t3;
t3 = t1;
Console.WriteLine($"t3: {t3.First} and {t3.Second}");
(double A, string B) t4;
t4 = t1;
Console.WriteLine($"t4: {t4.A} and {t4.B}");
このように代入の際に要素名は関係なく、要素の順番と型の互換性だけが問題となります。要素の内で一つでも変換できない要素があると次の例のようにコンパイルエラーになります。
(int i, string s) t1 = (0, 1); // int は string に変換できない
// error CS0029: Cannot implicitly convert type 'int' to 'string'
int? nullable_int = 1;
int i = 2;
(int x, int z) t3 = (i, nullable_int); // int? は int に変換できない
// error CS0029: Cannot implicitly convert type 'int?' to 'int'
要素をvarへ分解したり、既存の変数に分解できます。
(int, string, bool) t1 = (1, "One", true);
// varに分解する
var (v1, v2, v3) = t1;
// 要素の一部をvarで受ける
(var v4, var v5, bool b) = t1;
// 既存の変数に分解する
int i1;
string s1;
bool b1;
(i1, s1, b1) = t1;
tupleの等値性
C#7.3以降 tuple で == と != 演算子をサポートして等値性を確認できるようになりました。
比較する tuple は同じ数の要素を持っている必要があります。要素数が異なるとコンパイルエラーになります。
(int a, byte b) left = (5, 10);
(long a, int b) right = (5, 10);
Console.WriteLine(left == right);
// true
Console.WriteLine(left != right);
// false
var t1 = (A: 5, B: 10);
var t2 = (B: 5, A: 10);
Console.WriteLine(t1 == t2);
// true
Console.WriteLine(t1 != t2);
// false
この例での比較は == 演算子の場合には、left.a == right.a && left.b == right.b のようになります。
!= 演算子の場合には、left.a != right.a || left.b != left.b のようになります。
System.TupleとSystem.ValueTupleの違い
System.Tupleは参照型のクラスで要素は変更不可(イミュータブル)です。System.ValueTupleは値型の構造体で要素は変更可能(ミュータブル)です。ただし、ValueTupleを関数の戻り値にする場合、要素を直接変更しようとするとエラーになります。
ValueTuple | System.Tuple | |
構造体 | クラス | |
値型 | 参照型 | |
メンバーの命名 | できる | 不可、メンバーはItem1, Item2…で参照 |
メンバーの不変性 | ミュータブル 可変 | イミュータブル リードオンリー |
メンバーの実装 | フィールド | プロパティ |
対応C#言語バージョン | C# 7.0以降 | |
要素数の制限 | 7つまで |
参照

コメント