ラムダ式とは?

ラムダ式には2通りの使い方がある。

  1. 匿名delegateの進化としての使い方
  2. 式ツリーとしての使い方

式ツリーというのはコンパイラなどのように式そのものを分解して解釈するようなプログラムを書く場合に式そのものをデータとして保持するような仕組みで利用されるらしい。ちょっと分野が特殊なので(コンパイラでも開発する人にしか意味は無さそう?)とりあえず今のところはあまり深入りせずにおいておく。(いやもちろんプログラムオタクの私は近いうちに軽く深入りさせてもらおうと思っている)
まず匿名delegateとしての使い方。たとえばこういうdelegate型があったとする。

delegate int PointHandler(int x, int y);

画像処理とかやってると結構使いよさそうなdelegate型。これをC#2.0ではこういう具合に使っていた。

PointHandler handler = delegate(int x, int y) { g.SetPixel(color, x, y); };
if (flag == 0) handler = delegate(int x, int y) { h.SetPixel(color, x, y); };

これはたとえばグラフィックオブジェクトgとhがあってフラグflagによってhandlerの中身を入れ替えるような処理だ。これがC#3.0のラムダ式だとこうなる。

PointHandler handler = (x, y) => g.SetPixel(color, x, y);
if (flag == 0) handler = (x, y) => h.SetPixel(color, x, y);

この変化においていくつかの点に注意したい。

  1. delegateというキーワードが無くなっている
  2. =>というキーワードが増える
  3. 引数リストの型intが無くなっている
  4. delegate本体の{}が無くなっている

C#3.0の方向性というと「コンパイラがわかることはコンパイラに任せる」に尽きるようで、型については先のdelegate型のPointHandlerの宣言にintが二つあると明記しているので、使用時に省略したとしてもコンパイラには型を調べだす方法があるというのがこの型省略の仕様なのだ。
次にdelegateというキーワードが無くなった事。これは文意からこれが正しく匿名delegateと判断できるような文法さえ確立されてあるならば省略できておかしくなかった話だ。C#2.0の匿名delegatedelegateキーワードを略すると単なるブロックだけが残ってしまい、文法的に別の解釈にあたってしまい(たとえば初期化の{}と判断されたりするかもしれない)やむなくあの位置にdelegateというキーワードが存在していたのだと思われる。今回は=>という新しい演算子の導入により文意が匿名delegateつまるところラムダ式だと判断できるのでdelegateが消えているのだ。{}が無くなっているのは同じような「略しても文法的に別の文意に混同しないなら略したい」という意図から略されているだけである。通常のif文の本体部分の{}の省略規則と同じで、ラムダ式の本体が1つの文であればこのように{}は略せるのだ。匿名delegateでは{}は関数本体の{}の規則が適用されているので1つの文であっても決して省略することはできなかった。
さて、さらにこのラムダ式は引数が一つの場合には引数リストの()さえ略することを許される。

(x) => x + 1
は
x => x + 1
と書ける
復習のためにこれは逆にC#2.0では
delegate(int x) { return x + 1; }
とかをあらわす(引数の型intはこの場合は例である。先にも書いたとおりラムダ式では使っている場所だけ見ても引数の型が分からない場合が多い。いやもちろんコンパイラにはわかっていなければコンパイルは出来ない。ここがVBのような動的型を受け入れる言語とは違うところだね。)

引数が無いばあいには()だけを書く。

() => Console.Write("引数が無い場合")

ここで一つ疑問に思うものがあったのでちょっと実験した。


匿名delegateではたとえば引数を本体で使用しない場合には省略できるという機能があった。

delegate int Handler(object sender, EventArg e);
のとき、
Handler handler = delegate(object sender, EventArg e) { PrinterSetup(); };
は
Handler handler = delegate { PrinterSetup(); };
と書ける

果たしてラムダ式ではこの場合はどうなるのだろう?実際調べてみたのだが引数リストを略するとコンパイルできなかった。

Handler handler = (sender, e) => PrinterSetup();
は
Handler handler = () => PrinterSetup();
と書けない・・これは引数が無いラムダ式と解釈されるから。
さらに、
Handler handler = => PrinterSetup();
とも書けない。見た目にもいかにもエラーになりそうだが実際コンパイルエラーになる。

さてさてこの場合にはやっぱり匿名delegateの方がちょっぴり優れているのだろうか?それとも私がラムダ式を理解しきれていないのだろうか?もうちょっと勉強を進めたらもしかしたら答えが分かるかもしれない。(わからないかも知れないしこれで正解なのかもしれない)