1. タプルの命名:分解のためのコンテキスト
前回のレクチャーで、タプルの要素に名前を付けるやり方をやったよね。これでコードがめっちゃ読みやすくなる。無機質な Item1 や Item2 じゃなくて、意味のある名前(たとえば person.Name みたいな)でアクセスできる。特に、メソッドの戻り値やパラメータ、プロパティとして使うときは、要素に名前を付けるのが超大事。命名が、これからやる分解(deconstruction)を便利にするカギなんだ。
ちなみに、名前はタプルリテラルで初期化するときや、メソッド・プロパティ・フィールドの戻り値のシグネチャで付けられるよ。
メソッドの例: メソッドの戻り値で名前付きタプルを使う
public static (int 年齢, string 名前) GetPetInfo()
{
return (年齢: 5, 名前: "バルシク");
}
// 使い方:
var info = GetPetInfo();
Console.WriteLine($"{info.名前} — {info.年齢} 歳");
フィールド/プロパティの例: フィールド/プロパティで名前付きタプル
public (int 幅, int 高さ) 画像サイズ = (1024, 768);
覚えておいて: 名前を明示的に付けなかった場合、要素は Item1、Item2 みたいにアクセスできるまま。特にタプルを後で使うとき、これだとコードが読みにくくなる。だから、意味が分かりにくいときは、できるだけ要素にちゃんと名前を付けるのがおすすめ!
2. タプルの分解(deconstruction)
分解(deconstruction)って何?
分解(deconstruction)は、タプルをバラして個別の変数にすること。つまり、(年齢: 5, 名前: "バルシク") みたいなタプルから、年齢 と 名前 という2つの変数を取り出せるってこと。
イメージ: タプルはラベル付きの箱みたいなもの。分解は、その箱の中身を一気に机の上に並べる感じ!
分解の書き方(シンタックス)
var pet = (年齢: 5, 名前: "バルシク");
var (年齢, 名前) = pet;
Console.WriteLine($"{名前} — {年齢} 歳");
これで2つの変数 年齢 と 名前 ができた。タプルから値をもらってるだけ。左側の名前(年齢, 名前)は、タプルの中の名前と一致しなくてもOK。新しいローカル変数を作ってるだけだよ。
関数の戻り値の分解
複数の値を返すためにタプルを使うことが多い。そういうとき、分解がめっちゃ便利!
public static (double 最小, double 最大) GetMinMax(int[] データ)
{
int 最小 = データ.Min();
int 最大 = データ.Max();
return (最小, 最大);
}
var 数字たち = new[] { 1, 2, 3, 4, 5 };
var (最小値, 最大値) = GetMinMax(数字たち);
Console.WriteLine($"最小: {最小値}, 最大: {最大値}");
分解するとき、変数名はその場に合わせて好きに付けてOK!
var で分解
var (a, b) = (10, 20); // int a = 10, b = 20
分解と discard _
タプルの全部の要素が必要なわけじゃないときもあるよね。そんなときは _(discard)で無視できる。 タプルの中の discard(_)は、「ここに要素あるのは知ってるけど、変数いらないから作らないで」って意味。
つまり、タプルを分解しつつ、いらないところは「穴」を開けてスルーできる。シンプルで便利!
var pet = (年齢: 5, 名前: "バルシク", 幸せ: true);
var (年齢, _, 幸せ) = pet; // 年齢と幸せだけ、名前は無視
豆知識:discardは、いらない変数で名前空間を汚さないためによく使われるよ。
foreach で分解
C#では、タプルの配列を foreach の中で直接分解できる!
var ペットたち = new (string 名前, int 年齢)[]
{
("バルシク", 5),
("ムシャ", 3),
("ジョニー", 7)
};
foreach (var (名前, 年齢) in ペットたち)
{
Console.WriteLine($"{名前} — {年齢} 歳");
}
3. 要素名と型付けの仕組み
名前付き要素のふるまい
タプルの要素名は「シンタックスシュガー」つまり人間のための便利機能。コンパイル時には全部 Item1、Item2 みたいな内部フィールドになるけど、IDEやコンパイラが名前を覚えててくれるから、ちゃんと使える。
型の互換性とキャストへの影響
要素数と型が同じなら、名前が違ってもコンパイラ的には同じ型として扱われる。要素名は型の定義には含まれないよ:
var t1 = (X: 42, Y: 13);
var t2 = (A: 42, B: 13);
t1 = t2; // OK
Console.WriteLine(t1.X); // 42
ただし、式やIntelliSenseのヒントでは、左側(代入先)の名前が使われるよ。
暗黙的・明示的な名前の指定
もうやったけど、念のため。タプルは一部だけ名前を付けたり、全く付けなかったりもできる。その場合は Item1 みたいになる。
var 点 = (X: 10, 20); // XとItem2
Console.WriteLine(点.X); // 10
Console.WriteLine(点.Item2); // 20
アドバイス: タプルに2つ以上の要素があるときや、値の意味が分かりにくいときは、必ず名前を付けよう!
4. 名前や分解でよくあるミスや注意点
ミス1: 名前の「引き継ぎ」
先に名前付きでタプルを宣言して、あとで名前なし(または違う名前)のタプルを代入しても、IntelliSenseでは最初の名前が表示される。例:
var オリジナル = (X: 1, Y: 2);
var 別名 = オリジナル; // 別名.X == 1, 別名.Y == 2
オリジナル = (10, 20); // 名前なしタプル
Console.WriteLine(別名.X); // まだXでアクセスできる(名前は残る)
ミス2: 要素名の重複
2つの要素に同じ名前は付けられない。コンパイラが「Duplicate tuple element name」って怒る:
var ダメなタプル = (A: 1, A: 2); // エラー CS8122: Duplicate tuple element name 'A'
ミス3: 要素数が合わない分解
分解するときは、変数の数とタプルの要素数がピッタリ一致してないとダメ。違うとエラーになる:
var pet = (年齢: 5, 名前: "バルシク");
var (年齢, 名前, 気分) = pet; // エラー CS8124: Tuple must contain exactly 3 elements
ミス4: discard _ の使い方ミス
_ で要素を無視できるけど、それぞれの場所ごとに独立してる。1つの _ で複数の要素をまとめてスキップはできない。例えば、2つの要素を1つの _ で飛ばそうとするとエラー:
var データ = (1, 2, 3);
var (_, x, _) = データ; // OK: 1番目と3番目をスキップ
var (_, _) = データ; // エラー CS8124: Tuple must contain exactly 2 elements
GO TO FULL VERSION