xin9le.net

Microsoft の製品/技術が大好きな Microsoft MVP な管理人の技術ブログです。

値の破棄

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 レベルでは一切変更がありません。