【C#】ムズカシイ!をなくす「ラムダ式」解説

「C#のラムダ式が難しい!わかりづらい!」このように感じる人は多いでしょう。筆者の周りの人を見ていても、ラムダ式を「ちゃんと使いこなしている人」は意外と少ないように感じます。

しかしC#を使いこなすためには「ラムダ式」は絶対に理解しておくべき内容になります。というのも、C#における強力な機能にLINQがあるからです。それに加えてラムダ式を記述できるようになれば、「関数型プログラミング」の手法を取り入れて、ソースを簡潔に記述しやすくなるというメリットもあります。

この記事ではC#におけるラムダ式の基本を解説していきます。ラムダ式に苦手意識を持っている人は、この記事を読んでマスターしてもらえればと思います。

ラムダ式の基本知識

まずは「ラムダ式」ってどんなものだろうか?という部分から解説していきます。ラムダ式はC#3.0から導入された機能で、無名のメソッドを簡潔に表現するための記法になります。ラムダ式を使えば以下を簡潔に表現することができます。

  • デリゲート(delegate)
  • LINQクエリ
  • イベントハンドラ

ラムダ式の概念は数学と計算理論で広く使われる「ラムダ計算(Lambda Calculus)」が由来となっていますが、特に必須知識ではありません。ラムダ式では「ラムダ演算子」と呼ばれる独特な演算子を使用します。C#におけるラムダ演算子は「=>」と記述し、=> を境として、引数(入力値)と本体(式)を分ける働きをします。

ラムダ演算子の特徴

ラムダ式で使用するラムダ演算子の特徴を知っておきましょう。以下に内容を並べてみました。

  • ラムダ演算子は「=>」で記述する
  • ラムダ演算子は「goes to」と読む
  • =>の左がパラメータ(引数)である
  • =>の右が式(本体)である

なんとなく「聞いたことあるな」くらいで知っておいてもらえたらと思います。一般的なイメージは以下の通りになります。

(パラメータ) => (式);

ラムダ式が進化してきた過程

C#におけるラムダ式は「デリゲート」に始まり「匿名メソッド」、そして「ラムダ式」と進化を経てきた経緯があります。もともとラムダ式はデリゲート(委譲)を根底として、メソッド自体をオブジェクトのように扱えるために進化してきたのです。

1.デリゲート

デリゲートは処理の委任・委譲という意味があります。C#ではdelegate型変数にメソッドを登録することで、メソッドの処理自体を参照できるような仕組みになります。

//デリゲート
TestDelegate func0 = new TestDelegate(Test);
TestDelegate func1 = Test;

//デリゲート変数
delegate void TestDelegate(string str, int i);

//func0, func1の処理部分
static void Test(string s, int i)
{
    Console.WriteLine($"Hello,{ s }. It's Func{ i }!");
}

//Check each output.
func0("John Doe", 0);
func1("John Doe", 1);

デリゲート型は初期バージョンの C# から備わっていた機能ではあるものの、処理のロジックごとにメソッドを記述して事前に用意しておく必要があるなど、使い勝手が良いものではありませんでした。

上記のサンプルでは「Test」で記述したメソッドを、func0、func1の実処理部分として代入しています。デリゲートの引数とメソッドの引数が一致しているためデリゲートに代入することができる仕組みです。

2.匿名メソッド

匿名メソッドはその名の通りメソッド名を「匿名」にした状態でメソッドを定義できる機能でした。C# のデリゲートに続いて実装されたものになります。匿名メソッド以外にも「無名関数」とも呼ばれていて、デリゲート型の変数に対して、直接処理ロジックを記述できるようになった機能です。

//デリゲート変数
delegate void TestDelegate(string str, int i);

//匿名メソッド
TestDelegate func2 = delegate (string s, int i) 
{ 
    Console.WriteLine($"Hello,{ s }. It's Func{ i }!");
};

func2(name, 2);

左側がデリゲート型の変数で、イコールを挟んだ右側が処理ロジックの記述になっています。 戻り値がある場合も以下のように記述でき、戻り値のあるデリゲートの型を定義しておくことで戻り値の記載は不要になるという特徴があります。

delegate bool RetBoolDelegate();
private void hoge()
{
	 RetBoolDelegate func5 = delegate () { return false; };
}

これが「匿名メソッド」の記述方法です。ここまでは割とシンプルに理解できるのではないかと思います。この次に発展してでてきたのが「ラムダ式」ということになります。

3.ラムダ式

「匿名メソッド」を用いたデリゲートの記述方法を、さらに拡張した記述方法が「ラムダ式」になります。ラムダ式はアロー演算子を使用して、デリゲートの型を省略して記述することが可能となります。

//デリゲート変数
delegate void TestDelegate(string str, int i);

//ラムダ式
TestDelegate func3 = (s, i) => Console.WriteLine($"Hello,{ s }. It's Func{ i }!");

左辺はデリゲート型の変数を定義し、右辺が処理ロジックの記述部分を示しています。アロー演算子を使用する場合は、「=>」の左側が引数で右側が処理部分です。上記のサンプルは「匿名メソッド」のサンプルと同じ処理内容であることがわかると思います。

またラムダ式では引数を取らない場合は「()」のみを記述します。さらに「{ … }」でくくる場合は複数行の記述が可能となります。=> を使って記述する場合は「return」キーワードが不要であるなど、ラムダ式になると記述方法が多様になります。

RetBoolDelegate func6 = () => true;
RetBoolDelegate func7 = () => { return true; };
RetBoolDelegate func8 = () => 
{ 
    if(true) return true;
};

ラムダ式の様々な記述方法については次項に記載しますので、こちらも併せて確認しておくとよいでしょう。

ラムダ式のいろいろな記法

ラムダ式にはいろいろな書き方がありますので、代表的なものサンプルコードも交えて紹介していきます。C#のラムダは柔軟に対応してくれます。以下のサンプルではラムダ式を支える Func・Action といったキーワードも出てきます。

Func や Action は C# でよく使用される機能はとても重要なので、ラムダ式とセットで理解しておくとよいでしょう。

1.単純なラムダ式

シンプルなラムダ式が以下のサンプルです。2つのint型の引数を受け取り、int型の値を戻り値とするラムダ式です。パラメータは int 型として定義されています(Funcの後に記載の2つの int が該当する)。

Func<int, int, int> add = (x, y) => x + y;
Console.WriteLine(add(3, 4));  // 出力: 7

「add(3, 4)」と記載している通り、入力値は int 型の 3 と 4 となります。なお、式が複数でない場合のラムダ式では、return で戻り値を記述する必要はありません。

2.引数が1つのラムダ式

引数が一つの場合は「( )」の記載が不要となります。int型の引数を一つ受け入れ、int型の戻り値を返却するサンプルです。以下のコードではsquareメソッドに「3」を引数として渡しています。

Func<int, int> square = x => x * x;
Console.WriteLine(square(3));  // 出力: 9

3.引数のないラムダ式

引数のないラムダ式は「( )」の省略ができません。Actionは引数も、戻り値もない void 型を定義します。

Action sayHello = () => Console.WriteLine("Hello!");
sayHello();  // 出力: Hello!

4.複数の処理を含むラムダ式

ラムダ式内で複数の処理を行わせたい場合は中括弧( { } )を使用することで記述できます。Actionで定義しているため、引数も戻り値もないラムダ式です。これがFuncであれば return を使用して確実に戻り値を記述する必要があります。

Action multiStatement = () =>
{
    Console.WriteLine("Statement 1");
    Console.WriteLine("Statement 2");
};
multiStatement();

5.LINQでラムダ式の利用例

ラムダ式はLINQにおいての利用頻度がかなり高いので絶対に知っておくべきです。Where句は Predicate を受け取るので、かならず bool で返せるラムダ式を書く必要があります。

List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };
var evenNumbers = numbers.Where(n => n % 2 == 0);

上記のサンプルで出てきた Predicate や LINQ については、以下の関連記事をチェックしておきましょう。特にLINQはC#を使いこなすために超重要な機能ですので、しっかりと理解しておくことが望ましいです。

6.イベントハンドラ

イベントハンドラとしてラムダ式を記述することが出来ますが、これはイベントリークに繋がる可能性もあるため知っておくだけに留めておくとよいでしょう。

Button myButton = new Button();
myButton.Click += (sender, e) =>
{
    Console.WriteLine("ボタンがクリックされました!");
};

イベントハンドラの処理を簡潔に記述できることが分かります。ただし、イベントハンドラのラムダ式は、イベントのアタッチが出来ないのでオススメする記述方法ではありません。あくまでも一例として知っておくべきです。

まとめ

ここまでラムダ式について解説してきました。「ラムダ式」は式が読みづらく、分かり難いと思う人が多いのも理解できます。とはいいながらも、利便性は高いため使っていきながら記述方法に慣れてもらえれば、「無名のメソッドを即座に使用できる」という利便性が理解できると思います。

それに加えてソースコードを簡潔に記述できるというメリットもあります。デリゲートやLINQクエリなどにおいても「ラムダ式」は必須の知識となります。ぜひともこの記事を読んで理解していただき、積極的に取り入れながら、徐々にラムダ式を使いこなせるようになってもらえればと思います。理解が進むことで、より効率的なC#プログラミングが楽しめるはずです。