読者です 読者をやめる 読者になる 読者になる

銀の弾丸

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

C#のラムダ式はAction・Funcと一緒に理解を深めるとヨロシイようで

C# VisualStudio

年に一度のパートタイム・シーシャーパー(C#erと書くらしいですね)ですが、今年は少し期間が長く、去年よりは深い仲になれそな予感がしています♪

とはいえパートタイム・シーシャーパー(もういい)なので最新の動向を追いかけるのに四苦八苦。

てことで、中途半端にしか理解していなかった Action、Func、ラムダ式などについて、現場で実際に使って理解したことを書いておきます。

専門の人にとっては「何だ?今さら?」的な記事だと思います。そこんところはお手柔らかに。

ちなみに現場で毎日、MVVM警察に怯えております。 「おいそこ!なんでクリックイベント拾ってるんだ!今すぐコマンド使いなさい!」みたいなビクビクもんです。

いくつになってもお勉強です(泣

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

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));
};

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

これは無名関数ではない

曖昧な理解で、即時関数的な使い方をしようとして、「あれ?なんか違う」と混乱。 コピペで、どうにか動かせるけど「ここでは、なぜ、そう書かなければならないのか」まで、スッキリ理解できていないっていう状態。

C#ラムダ式って「無名関数でしょ?」的な思い込みがあったんですね。 でもそれは間違い。ラムダ式=無名関数ではありません。

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

結局、ラムダ式を問題なく自由に使えるようになったのは、ActionクラスとFuncクラスを理解して、一緒に使うものだと認識してからでした。

それまでは、ラムダ式単体で見て「型が明示されていないのに、どうコンパイルされて実行されているのだ?」と不審に思っていました。 しかし、その、引数の数や型、戻り値の型など、欠落している情報は一緒に使われている ActionFunc、またはデリゲートで明示されているってことですね。

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

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

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

その証拠に、JavaScriptの即時関数みたいなのは、C#ラムダ式だけでは実装不可

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

以下のように、一旦Actionインスタンスを作ってからでないと実行できない。Actionで第一引数がintであることを明示していますね。(敢えて似せて書いています。丸かっことかね)

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

JavaScriptでの無名関数は関数オブジェクトだけど、C#ラムダ式に直接対応するオブジェクトはないってこと。 ちなみにJavaラムダ式は無名クラスのインスタンスだということです。

Action クラス

ACTION
ACTION
posted with amazlet at 16.09.22
B’z
VERMILLION RECORDS(J)(M) (2007-12-05)
売り上げランキング: 22,294

で、そのActionクラスですが、戻り値のない処理を記述するためのクラスですね。

Actionだけだと、引数もなし。

// 引数無しのActionをラムダ式で生成
Action action = () => {
    foo.bar();
};

引数つけたいなら、Action<引数型リスト>。例えば、

//引数を取るActionをラムダ式で生成
Action<string, int> action = (name, age) => {
    Console.WriteLine(string.Format(
        "{0} is {1} years old.", name, age));
};

てな感じ。

Func<TResult>クラス

グレイト・ヒッツ
グレイト・ヒッツ
posted with amazlet at 16.09.22
T.レックス
インペリアルレコード (2005-05-25)
売り上げランキング: 3,577

Funcは戻り値があるメソッドを表す。戻り値がboolで、引数のないFuncは、

//戻り値がboolのFuncをラムダで生成
Func<bool> func = () => {
    return true;
};
//戻り値がboolで引数付きのFuncをラムダで生成
Func<string, int, bool> isAround50 = (name, age) =>
    Console.WriteLine(string.Format(
        "{0} is {1} years old.", name, age));
    if(45 <= age && age < 55) {
        return true;
    }
    return false;
};

とかですね。

これがたとえば、メソッドのパラメータだとしても、考え方は同じで、以下のように書くわけです。

Task<bool> task = new Task<bool>(() =>
{
    return execute.Invoke();
});
chainedTask._task.Start();

つまり、Task<bool>のコンストラクタの第一引数の型はFunc<bool>だと推測できる。

そのほか無駄話など

Func<void>ではダメなんですか?

個人的な好みとしてですが、戻り値の有無でクラスを分けずに、Func<void>を認めて、Actionクラスはなくてよいでしょと思っていますが、 そもそもジェネリックの型パラメータにvoidは無理なのかも。 VBでもFunctionとSubに分かれているし、マイクロソフトさんは昔から分けたい派なのかと思っていたけど、言語的制約か。

シンタックス・シュガー

シンタックス・シュガーって「糖衣構文」と訳されると知って軽くショックを受けている。

その昔、Perlラクダ本で「構文糖」と目にして以来、そのまま使っていたんだけど、21世紀の現代的には「なにそれおじさん」なんだろうか?