ラムダ式、匿名メソッド、デリゲート

10分読んで、これだけ覚えておけば大丈夫と思うところをご紹介したいと思います。

まず以下のコード、コンパイルできませんがどう修正したらよいかわかりますか?

 TestForm.cs 一部抜粋

        private void button1_Click(object sender, EventArgs e)
        {
            ThreadPool.QueueUserWorkItem(new WaitCallback(AddDrives));
        }

        private void AddDrives(object stateInfo)
        {
            foreach(string drive in Environment.GetLogicalDrives()){
                comboBox1.Invoke(() => comboBox1.Items.Add(drive));
            }
        }

そりゃわかるよってお方にはこのブログのエントリは必要ありません;
上のコードでやろうとしていることは、Formのボタン押下イベントハンドラで別スレッドを立ち上げています、別スレッドからメインスレッド上のcomboBox1を操作するため、Invokeしてメインスレッドで実行してもらう関数を渡しています。では何がダメか?ダメなのはラムダ式です。
Invokeは引数に”デリゲート“をとりますが、()=>comboBox1.Items.Add(drive)はラムダ式なのです。ではどうすればよいか、上の例に対する修正案とコードコメントで示していきます、合わせて読み進めてくださいね。

        private void AddDrives(object stateInfo)
        {
            foreach(string drive in Environment.GetLogicalDrives()){
                // 【0】上記のとおりNG
                //comboBox1.Invoke(() => comboBox1.Items.Add(drive));

                // 【1】ラムダ式は下記2通りの意味を持つ
                Action act = () => comboBox1.Items.Add(drive);
                Expression<Action> actExp = () => comboBox1.Items.Add(drive);
                //  1-1.<OK> Actionに明示的に代入すると、デリゲートになる。
                comboBox1.Invoke(act);
                //  1-2.<NG> Expression<Action>は式ツリーで、デリゲートではない。
                //comboBox1.Invoke(actExp); // compile error!
                //      <OK> 式ツリーをCompileすると、デリゲートになる。
                comboBox1.Invoke(actExp.Compile());
                //  1-3.<OK> だから上の【0】NG例は、デリゲートと明示すればよかった。
                //      ※ActionとMethodInvokerは同じもの。補足後述。
                comboBox1.Invoke((MethodInvoker)(()=>comboBox1.Items.Add(drive)));

                // 【2】匿名メソッドは...?
                //  2-1.<NG> 匿名メソッドもデリゲートではない。
                //comboBox1.Invoke(delegate() { comboBox1.Items.Add(drive); });
                //  2-2.<OK> でもラムダ式同様、デリゲートにすることができる。
                comboBox1.Invoke((MethodInvoker)(delegate() { comboBox1.Items.Add(drive); }));
                //  2-3.<OK> デリゲートにできるので、それを渡すこともできる。
                Action act2 = delegate() { comboBox1.Items.Add(drive); };
                comboBox1.Invoke(act2);
                //  2-4.<OK> あれ、こうみると匿名メソッドってラムダ式みたい...?
                Expression<Action> actExp2 = () => comboBox1.Items.Add(drive);
                comboBox1.Invoke(actExp2.Compile());
            }
        }

ちょっと補足を。まずMethodInvokerとAction、MSDNのControl.Invokeのにこう記載があります。
methodinvokerとactionのちがい
ということで、同じですがMethodInvokerのほうがパフォーマンスで分があるようです。

話を戻して、ラムダ式と匿名メソッドの違いってなんなのさ?ってことには匿名メソッドのにこう記載があります。
lamdaanddelegate

つまり、ソースで言うとこういうこと。

        private void TestLamdaAndDelegate(object stateInfo)
        {
            // OK
            DummyDelegate dummyDelegate1 = delegate(string a) { ;/*do nothing,*/};
            // OK
            DummyDelegate dummyDelegate2 = delegate { ;/*do nothing,*/};

            // OK
            Action<string> dummyLamda1 = delegate(string a) { ;/*do nothing,*/};
            // NG.
            //Action dummyLamda2 = delegate(string a) { ;/*do nothing,*/};
        }

だからイベントのnullチェックを省くために、シグネチャを気にせず

public event EventHandler hogehogeEvent = delegate { };

なんてしておくと、if( null != hogehogeEvent) なんてムダコードが省けてすっきりするわけです。

ここで出てきた、Actionデリゲートの他に, Funcデリゲート、Predicateデリゲート、これらは覚えて損はないので覚えておきましょう。
1.Actionは戻り値のないデリゲートです。
2.Funcは戻り値のあるデリゲートです。
3.Predicateはboolを返すデリゲートです。

Action, Funcはそれぞれ引数を0~複数個とることがあるので、引数ゼロ個から順に、

・Action, Action<arg1>, Action<arg1, arg2>, Action<arg1, arg2, arg3> ,…
・Func<ret>, Func<arg1, ret>, Func<arg1, arg2, ret>, Func<arg1, arg2, arg3, ret>,…

となります。似ていて覚えやすいですね。Actionはvoid, Funcは最後の型引数が戻り値になります。

Predicateは何かを受け取ってそれが条件を満たすかどうかを判断する式です、Predicate<T>はFunc<T, bool>と考えればよいでしょう。

広告

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト / 変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト / 変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト / 変更 )

Google+ フォト

Google+ アカウントを使ってコメントしています。 ログアウト / 変更 )

%s と連携中