銀の弾丸

プログラミングに関して、いろいろ書き残していければと思っております。

C#のラムダ式はAction・Funcと一緒に理解する

f:id:takamints:20160922160154p:plain
photo credit: Imperial Shuttle via photopin (license)

C#ラムダ式をスッキリ理解する方法です。

何事も表面的な丸暗記でなく、基礎からキッチリ理解するほうが結果的には早道ですね。

C#ラムダ式に関しては、ActionクラスととFuncクラスをしっかり理解しておけば、それほど難しくはないはずです。

C#のActionクラスは、戻り値のないメソッドを表すクラスで、Funcは戻り値のあるメソッドです。

どちらもジェネリッククラスで、Actionは引数リスト、Funcは戻り値の型と引数リストをジェネリックパラメータで指定します。

Actionはメソッドの動作内容に注目した名前であり、Funcは数学的な関数として評価値を持つ(=戻り値がある)ということですね。

この2つのクラスについては以下のページで詳しく書いていますので参照してください。

takamints.hatenablog.jp

目次

C#ラムダ式

C#逆引きレシピ
C#逆引きレシピ
posted with amazlet at 16.09.22
arton
翔泳社
売り上げランキング: 97,981

ラムダ式の記法的には、以下のような感じ。引数がひとつなら丸かっこは不要とか、波かっこの中身が単一の文なら(複文でないなら)波かっこは要らないとか、いろいろあるんだけど、基本はこちらでOKです。

(name,age) => {
    Console.WriteLine(
        string.Format(
            "{0} is {1} years old",
            name, age));
};

型とか指定されていないし、戻り値ってありなの?無しなの?どうでもいいの?てな具合に、全く情緒が安定しない代物ですが・・・

ラムダ式は即時呼び出しできません

JavaScript的にラムダ式を直接呼び出そうとしてエラーになって「何がダメなの?」と混乱しました。 「C#ラムダ式ってJavaScriptの無名関数と一緒でしょ?関数オブジェクトそのものでしょ?」的な思い込みがあったんですね。

でもそれは間違い。C#ラムダ式は、それそのものを呼び出すことができません。

ラムダ式というランタイムオブジェクトは無い

C#ラムダ式は、ActionクラスかFuncクラスのインスタンスを生成するためのものなんですよね。

そもそもこれまで、ラムダ式を見たとき「型が明示されていないのに、どうコンパイルされて実行されているのだ?」と思っていたのですが、正しく動いているコードなら、一見欠落しているように見える引数や戻り値の型情報は、一緒に使われている ActionFunc、またはデリゲートで推測可能になってるはず。

ラムダ式はそれらのオブジェクトを生成するために使われるのですが、ランタイムに「ラムダ式」というオブジェクトとして存在しているわけではないということです。

単なる記法、シンタックス・シュガーです

つまり「ラムダ式は、デリゲートやActionやFuncを記述するためのシンタックス・シュガー」であって、それ自体はオブジェクトでもなんでもなく「単なる記法」というわけです。

その証拠に、JavaScriptの即時関数(以下)のようなコードは、C#ラムダ式では実装不可です。コンパイルが通りません。

//JavaScriptの即時関数呼び出し
var a = 1;
(function(b) {
    a += b;
}(2));
console.log("a:", a);// "a: 3"

C#では、一旦Actionインスタンスを作ってからでないと実行できません。

なぜかというと、ラムダ式だけでは、引数の型が特定できないからなんですね。

以下のコードでは、Actionクラスによって、第一引数がintであることが明示されている(あえて似せて書いていますので無駄に丸かっこが付いています)ので実行可能となるわけです。

int a = 1;
(new Action<int>(b => {
    a += b;
})(2));
Console.WriteLine(string.Format("a: {0}", a));

JavaScriptでの無名関数は関数オブジェクトですが、C#ラムダ式に直接対応するオブジェクトはないってことです。 ちなみにJavaラムダ式は無名クラスのインスタンスなんですね。 言語によって扱いが異なるのは興味深いがヤヤコシイ。