C#のラムダ式【=>】って何?

C#のコードを眺めていると

 

 

var ret = list.Select( n => n > 0 );

 

のような書き方を見る事があります。

この、メソッドの引数の部分。C#に慣れていない人は、なにこれ?ってなるんじゃないでしょうか?

 

これ、C#3.0から導入されたラムダ式と言います。

今回はこのラムダ式についてまとめてみようと思います。

その前にデリゲート(Delegate)を理解する

ラムダ式を理解する為には、まずデリゲート(Delegete)を理解する必要があります。

 

デリゲートを簡単に説明すると、関数変数のように扱う為のものです。

C言語に慣れている人には関数ポインタのようなものと言った方が分かるかもしれません。

 

変数のように扱えれば、関数の引数に関数を渡す事ができます。

引数に関数が渡せれば、計算式の一部だけを簡単に差し替える事ができます。

コードサンプル

以下の例はArrayListを継承したクラスですが、条件に合う項目だけを抜き出すSelectメソッドを作ってみました。この「条件に合う」という部分にdelegateを使っています。

    public delegate bool TestDelegate(object obj);


    public class TestList : ArrayList
    {
        public ArrayList Select(TestDelegate func)
        {
            ArrayList list = new ArrayList();
            foreach (object obj in this)
            {
                if (func(obj))
                {
                    list.Add(obj);
                }
            }
            return list;
        }
    }

 

このクラスを使った例が以下のものです。

匿名メソッドを記述して、値が0以上の項目を抜き出しています。

            var list = new TestList();
            list.Add(0);
            list.Add(1);
            list.Add(2);

            var result = list.Select(
                delegate (object o)
                {
                    int n = (int)o;
                    if (0 < n)
                        return true;
                    else
                        return false;
                });

ラムダ式は匿名メソッドを簡略化したもの

デリゲートが分かったところでラムダ式の話に移ります。

ざっくり言うとラムダ式は匿名メソッドを簡略化して記述したものになります。

上記の例を少し簡略化してみましょう。

            var list = new TestList();
            list.Add(0);
            list.Add(1);
            list.Add(2);

            var result = list.Select(
                delegate (object o)
                {
                    return (0 < (int)o);
                });

このdelegateキーワードを演算子=>に置き換えたものがラムダ式です。

            var result = list.Select(
                (object o) =>
                {
                    return (0 < (int)o);
                });

C#には型推論という機構があります。

この型推論機構のおかげでラムダ式は型の記述を省略する事ができます。

            var result = list.Select(
                (o) =>
                {
                    return 0 < (int)o;
                });

関数の中身が1行の場合、{}returnも省略する事ができます。

            var result = list.Select((o) => 0 < (int)o);

delegateの引数が1つだけの場合は()も省略する事ができます。

            var result = list.Select(o => 0 < (int)o);

ちなみにdelegateの引数が2つの場合は()が必要になります。

    public delegate bool TestDelegate(int n1, int n2);
            var result = list.xxxx((n1, n2) => (0 < n1) && (0 < n2));

定義済みdelegate(FuncとAction)

ここまでの例ではTestDelegateというのを自分で定義して使っていました。

引数の数が変わる度にいちいち定義するのはめんどくさい気がします。

 

そんな訳で、Microsoftは予めFuncActionというdelegateを定義してくれています。

戻り値を持つdelegateがFunc

戻り値を持たないdelageteがActionです。

 

定義を見てみるとジェネリックという機構をつかって様々な型に対応できるようになっています。

引数の数ごとにたくさん定義されているので、自分でdelegateを定義する必要はもはや無さそうです。

Func

    public delegate TResult Func<out TResult>();
    public delegate TResult Func<in T1, out TResult>(T1 arg1);
    public delegate TResult Func<in T1, in T2, out TResult>(T1 arg1, T2 arg2);
    public delegate TResult Func<in T1, in T2, in T3, out TResult>(T1 arg1, T2 arg2, T3 arg3);
          :

Action

    public delegate void Action();
    public delegate void Action<in T1>(T1 arg1);
    public delegate void Action<in T1, in T2>(T1 arg1, T2 arg2);
    public delegate void Action<in T1, in T2, in T3>(T1 arg1, T2 arg2, T3 arg3);
          :