ラムダ式に記述されている "_" (アンダースコア) の意味 - C#

ラムダ式に記述されている "_" (アンダースコア) の意味を紹介します。

概要

C#のコード中のラムダ式に "_" (アンダースコア) が記述されている場合があります。 昔のC#のコードにはなかった記述ですが、最近になり目に付くようになりました。この記事ではラムダ式中の "_" (アンダースコア) の紹介をします。

記述例

Func<int, int, string> constant = (_, _) => "ぺんぎんクッキー";

意味

_ は使用しない引数(パラメーター)を意味します。

次のプログラムを作成します。

UI

下図のフォームを作成します。
フォームにボタンを3つ、複数行のテキストボックスを配置します。

コード

下記コードを記述します。
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace LambdaExpressionsDiscards
{
  public partial class FormSimpleLambdaDicards : Form
  {
    public FormSimpleLambdaDicards()
    {
      InitializeComponent();
    }

    private void button1_Click(object sender, EventArgs e)
    {
      //Func<int,int> proc = (a) => a * a;
      Func<int, int> proc = a => a * a;
      textBox1.Text = proc(3).ToString();
    }

    private void button2_Click(object sender, EventArgs e)
    {
      Func<string> proc = () => "ぺんぎんクッキー";
      textBox1.Text = proc();
    }

    private void button3_Click(object sender, EventArgs e)
    {
      Func<string, string> proc = _ => "ぺんぎんクッキー";
      textBox1.Text = proc("");
    }
  }
}

解説

button1 のコードはint型のパラメーターを1つ受け取りint型を返すラムダ式の記述例です。
パラメータの数を2乗した値を返します。
  Func<int,int> proc = (a) => a * a;
  textBox1.Text = proc(3).ToString();
または、
  Func<int, int> proc = a => a * a;
  textBox1.Text = proc(3).ToString();

button2 のコードはパラメーターが無く、文字列型を返すラムダ式の記述例です。
  Func<string> proc = () => "ぺんぎんクッキー";
  textBox1.Text = proc();

string型のパラメーターを1つ受け取り、string型を返すラムダ式を次のコードで記述できます。
string型のパラメーターのあるラムダ式ですが、この実装では入力されたパラメーターは利用しません。 この場合に _ と記述することで、コードの保守性が高まります。また、パラメーター変数のメモリも確保されないため、コードが最適化されます。
  Func<string, string> proc = _ => "ぺんぎんクッキー";
  textBox1.Text = proc("");

実行結果

プロジェクトを実行します。下図のウィンドウが表示されます。


[button1]をクリックします。テキストボックスに 9が表示されます。入力パラメーター3を2乗した数値が表示されます。


[button2]をクリックします。テキストボックスに "ぺんぎんクッキー" の文字列が表示されます。ラムダ式の戻り値がテキストボックスに表示できています。


[button3]をクリックします。button2 をクリックした時と同様に "ぺんぎんクッキー"の文字列が表示されます。 _ のあるラムダ式でも同じように実行できていることがわかります。

どうしてこんな仕組みがあるのか?

先のプログラムで buttton3 の記述方法 Func<string, string> proc = _ => "ぺんぎんクッキー"; が動作することを確認しましたが、 同じ動作をbutton2の記述方法で表現できるため、あまり意味がないように思えます。具体的にどういったシーンで利用できるかを紹介します。

UI

下図のフォームを作成します。

コード

以下のコードを記述します。
OpClass.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace LambdaExpressionsDiscards
{
  public class OpClass
  {
    private string product;
    private string brand;
    public Func<string, string, string> CustomProc { get; set; }

    public OpClass()
    {
      product = "クッキー";
      brand = "ぺんぎん";
    }

    public string proc()
    {
      if (CustomProc != null) {
        return CustomProc(brand, product) + brand + product;
      }
      else {
        return brand + product;
      }
    }
  }
}
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace LambdaExpressionsDiscards
{
  public partial class FormEventLambdaDiscards : Form
  {
    public FormEventLambdaDiscards()
    {
      InitializeComponent();
    }

    private void button1_Click(object sender, EventArgs e)
    {
      OpClass op = new OpClass();
      textBox1.Text = op.proc();
    }

    private void button2_Click(object sender, EventArgs e)
    {
      OpClass op = new OpClass();
      op.CustomProc = (b, p) =>
      {
        if (b == "ぺんぎん") return "クールな"; else return "ふつうの";
      };

      //または
      //op.CustomProc = (b, p) => b=="ぺんぎん" ? "クールな" : "ふつうの";

      textBox1.Text = op.proc();
    }

    private void button3_Click(object sender, EventArgs e)
    {
      OpClass op = new OpClass();
      op.CustomProc = (b, _) =>
      {
        if (b == "ぺんぎん") return "クールな"; else return "ふつうの";
      };
    }

    private void button4_Click(object sender, EventArgs e)
    {

      OpClass op = new OpClass();
      op.CustomProc = (b, p) => "スーパー";
      textBox1.Text = op.proc();
    }

    private void button5_Click(object sender, EventArgs e)
    {
      OpClass op = new OpClass();
      //op.CustomProc = () => "スーパー";//エラー
      op.CustomProc = (_, _) => "スーパー";
      textBox1.Text = op.proc();
    }
  }
}

解説

OpClass は proc() メソッドで brand と product の変数を結合した文字列を返すロジックが実装されています。 OpClassには Func<string, string, string> 型のイベントが実装されており、イベントハンドラーの戻り値があれば、イベントハンドラーからの値と合わせて procメソッドの文字列を出力します。

button1 はイベントを利用せずに、OpClassのインスタンスを作成し、procメソッドを呼び出し、戻り値をテキストボックスに表示するコードです。
    private void button1_Click(object sender, EventArgs e)
    {
      OpClass op = new OpClass();
      textBox1.Text = op.proc();
    }

button2 はイベントを利用する例です。
イベントハンドラで、brand変数の値が "ぺんぎん"であった場合は、イベントの戻り値として"クールな" を返します。 proc メソッドの結果は "クールなぺんぎんクッキー" となります。
    private void button2_Click(object sender, EventArgs e)
    {
      OpClass op = new OpClass();
      op.CustomProc = (b, p) =>
      {
        if (b == "ぺんぎん") return "クールな"; else return "ふつうの";
      };
      textBox1.Text = op.proc();
    }

3項演算子を利用した場合の記述方法です。
   private void button2_Click(object sender, EventArgs e)
    {
      OpClass op = new OpClass();
      op.CustomProc = (b, p) => b=="ぺんぎん" ? "クールな" : "ふつうの";
      textBox1.Text = op.proc();
    }

上記のコードで問題なく動作しますが、イベントの2番目の引数 p はロジック内で利用していません。 ロジック内で利用していないことを示すため、変数のメモリを最適化するため、button2のロジックと同様の処理を次のコードで記述できます。
2番目の引数を _ と記述することで、このパラメータを使用しないことを明示できます。
    private void button3_Click(object sender, EventArgs e)
    {
      OpClass op = new OpClass();
      op.CustomProc = (b, _) =>
      {
        if (b == "ぺんぎん") return "クールな"; else return "ふつうの";
      };
    }

同様にイベントのパラメーターを全く利用しないイベントハンドラの場合は、2つの引数を _ で記述して、 パラメーターを使用しないことを明示できます。
    private void button4_Click(object sender, EventArgs e)
    {

      OpClass op = new OpClass();
      op.CustomProc = (b, p) => "スーパー";
      textBox1.Text = op.proc();
    }
    private void button5_Click(object sender, EventArgs e)
    {
      OpClass op = new OpClass();
      op.CustomProc = (_, _) => "スーパー";
      textBox1.Text = op.proc();
    }

なお、イベントハンドラのデリゲートの型はstring型の引数を2つ受け取ると決まっているため、 パラメータを使わないからといって、以下のコードを記述することはできません。(コンパイルエラーになります。)
    private void button5_Click(object sender, EventArgs e)
    {
      OpClass op = new OpClass();
      op.CustomProc = () => "スーパー";  //エラー
      textBox1.Text = op.proc();
    }

実行結果

上記のプロジェクトを実行します。下図のウィンドウが表示されます。


[button1]をクリックします。テキストボックスに "ぺんぎんクッキー" の文字列が表示されます。イベントハンドラを設定しない場合の動作です。


[button2]をクリックします。テキストボックスに "クールなぺんぎんクッキー" の文字列が表示されます。イベントハンドラを設定した場合の動作が 実装できています。


[buttton3]をクリックします。button2と同じ動作になります。


[button4]をクリックします。"スーパーぺんぎんクッキー"の文字列が表示されます。設定したイベントハンドラで固定の文字列を返す動作が実装できています。


[button4]をクリックします。[button4]と同じ動作が実装できています。


このページのキーワード
  • C# ラムダ式 アンダースコア
  • C# ラムダ式 _
著者
iPentecのメインプログラマー
C#, ASP.NET の開発がメイン、少し前まではDelphiを愛用
最終更新日: 2021-09-12
作成日: 2021-09-10
iPentec all rights reserverd.