1 年ほど前 C# 7.0 の新規のについていろいろ書いていたのですが、値の破棄 (discards) という機能について書いてなかったことに気が付きました!ということで、イマサラですが紹介します。
これまでの「値を使わない」ときの書き方
コンパイルを通すために引数を指定しなければならないけれど、その引数は今使いません。
…と言ったケースはままあります。そんな「値を無視したい」ときによく見かける実装が「_ (アンダースコア)」変数によるエスケープです。慣例レベルではありますが、「利用しない変数」であることを明示した書き方をします。例えば以下のような感じです。
int _; if (int.TryParse("123", out _)) {}
しかし C# においてアンダースコアは変数名として有効なので、複数の値を無視したい場合は以下のようにしなければなりません。
//--- 使いもしない変数なのにドンドン _ の数が増えていく...! int _; if (int.TryParse("123", out _)){} int __; if (int.TryParse("456", out __)){} int ___; if (int.TryParse("789", out ___)){} //--- 当然変数として有効なので、ここで触ることができる ___ += 10; Console.WriteLine(___);
「値の破棄」を明示したい
_
の数がドンドン増えるのはイヤだ- 値を無視するという意図の変数を誤って使わせたくない
そんなお気持ちを C# 7.0 が「discards (値の破棄)」としてサポートします。後述しますが、以下のようにいくつかのシチュエーションでのみアンダースコアが特別扱いを受けます。
//--- こんな関数があったとして private void OutVariable(out int value) => value = 123; //--- こんな風に書ける this.OutVariable(out var _); this.OutVariable(out _); //--- 触ろうとするとコンパイルエラー _ += 10; // そんな変数はないぞ!
実は out var _
のように型を書く必要はなく out _
だけでも OK です。また上記の場合 _
は変数として認められていないため、以下のような書き方をしてもコンパイルエラーになりません。
//--- こんな複数の値を引数から戻す関数があったとして private void OutVariable2(out int x, out string y) { x = 123; y = "abc"; } //--- 全部 _ ひとつで OK this.OutVariable2(out var _, out var _); this.OutVariable2(out _, out _);
discards が利用できる箇所
ザッと調べた範囲では、現状の C# 7.0 では以下の箇所で値の破棄の構文を使うことができます。
//--- 型分解のときに値を受けるけど捨てる var (name, _) = ("xin9le", 32); var (_, _) = ("xin9le", 32); //--- 型スイッチで string 型に対して処理したいけど値は使わない switch ("abc") { case string _: break; //--- ちなみにコレはコンパイルエラー //case _: // break; }
LINQ やイベントハンドラを記述する際のラムダ式でも頻繁に _
を使うことがありますが、C# 7.0 ではサポートされていません。これは今後のバージョンに期待…かもしれません。
逆コンパイル
この機能がどうやって実現されているのか、いつも通り逆コンパイルしてのぞいてみます。すると、以下のような C# 6 までのフツーのコードに展開されます。
//--- これは this.OutVariable2(out _, out _); //--- 素直にこう展開される int item1; string item2; this.OutVariable2(out item1, out item2);
つまり discards の構文であることを C# コンパイラが上手に判断して、良きに計らってくれているということですね。IL レベルでは一切変更がありません。