ForEach()とAction!(匿名メソッドつき)

ホームC#プログラミング入門講座 > ForEach()とAction!(匿名メソッドつき)

ForEach()とAction!(匿名メソッドつき)

ForEach()の概要

ForEach()メソッドは、C#2.0の新機能です。しかも、けっこう便利で、ジェネリックを強力にバックアップしてくれます。

ForEach()の役割は、foreachキーワードと同じような感じです。「コレクションの要素をひたすらAction!」と覚えておけば、まあ一生困らないでしょう。ですが、使い方はけっこう複雑です。なぜなら、C#で最も難しい「デリゲート」であるActionを使うからです。ForEach()は簡単ですが、デリゲートが難しい。

応用講義で解説しているんですが、もう1度デリゲートから解説しましょう。

デリゲートとは?

デリゲートとは「クラスもどきのメソッドの箱」です。クラスもどきのメソッド、なのですが、そこにメソッドを追加していける「箱」なのです。使うときはあたかもクラスのように使い、new で実体化します。サンプルを見てください。

using System;
using System.Collections.Generic;
using System.Text;

namespace GenericsVersion
{
    class Program
    {
        private static int T_amount = 0;
        private static int K_amount = 1;

        private static void Main(string[] args)
        {
            List<int> li = new List<int>();
            li.Add(1);
            li.Add(3);
            li.Add(5);
            li.Add(7);
            li.Add(9);

            Action<int> action = new Action<int>(Tashizan);
            action += new Action<int>(Kakezan);

            li.ForEach(action);

            Console.WriteLine("足し算=" + T_amount);
            Console.WriteLine("掛け算=" + K_amount);
        }

        private static void Tashizan(int i)
        {
            T_amount = T_amount + i;
        }

        private static void Kakezan(int i)
        {
            K_amount = K_amount * i;
        }
    }
}

これはForEach()のサンプルです。そして不思議なことに、この中ではデリゲートメソッドは定義されていません!! ですが、Actionというデリゲートを使っています。ここがまず、ポイントになるでしょう。

上の方のusingを見てください。その2行目。名前空間がありますね。

using System.Collections.Generic;

実は、Actionというデリゲートメソッドは、すでにこの名前空間の中で宣言されています。delegate というキーワードで、もう作られているのです。だから、それを使うだけ。

Action<int> action = new Action<int>(Tashizan);
action += new Action<int>(Kakezan);

この2行が、Actionデリゲートメソッドを使っているところです。デリゲートは、必ずクラスと同じように new を使います。括弧の中に入っている Tashizan は、このactionという箱に入れるメソッドです。

2行目は、箱の中にさらにKakezanというメソッドを追加しています。+= は追加の演算子でしたね。action というデリゲート用の箱(変数)には、Tashizan と Kakezan の両方がセットで入っているという状況です。

デリゲートというのは「クラスもどきのメソッドの箱」だと言いましたね。new を使うとこはクラスにそっくり、だけど中身はメソッド。まさに、「箱」なんです。

不思議なForEach()

デリゲートも不思議ですが、ForEach()はもっと不思議です。たった1行で、すごい働きをしてくれる。この1つのメソッドがすでにスーパーコンピューターとして独立しているような感じですね。

li.ForEach(action);

ForEach() は「クラスもどきのメソッドの箱」を受け取ります。action は何度も言っているように、「箱」です。箱の中には Tashizan() メソッドと Kakezan() メソッドの両方が入っています。

ForEach() が action を受け取ると、コレクションの要素を次々と箱の中のメソッドに入れていきます。li には奇数の数列が入ってますよね。まず、1です。ForEach() は action という箱を抱え込んで、その中の Tashizan() に 1 を渡します。Tashizan() は1を変数i に入れ、パラパラと計算します。

Tashizan() が終わったら、ForEach() は Kakezan() に1を渡します。Kakezan() は1を変数i(さっきのとは別物)に入れて、パラパラと掛け算します。

1が終わったら、3です。3も同じようにやって5です。7です。9です。終わりです。これで ForEach() という働き者は action という箱を手放します。

ForEach() が終わったときにはT_amountとK_amountにはそれぞれ計算結果が入っています。あとはそれを表示させるだけです。

私は最初、K_amountの初期値を0にしていて、掛け算の結果が0になってしまいました。プログラムというのはこういうちょっとしたミスで大きな差ができてしまいます。注意してください。

復習

今回はジェネリック自体より難しいですから、復習しましょう。

ForEach() は、Action型の箱を受け取ります。ここでは、action という名前にしました。

action という箱には、コレクションのそれぞれの要素に対して行いたいメソッドを入れておきます。入れるときに使うのは、デリゲートです。new を使ってあたかもクラスのように、デリゲートメソッドAction を使います。入れるメソッドの名前は、new Action(~) の括弧の中に書きます。

手順を逆に見直してみました。あとはForEach()がパラパラとその要素を放り込んでいくだけです。ここでは1,3,5,7,9をパラパラと入れていき、足し算と掛け算を別々にしました。

ForEach()のメリット

利点...。同じことはこれまでのforeachでもできます。ですが。

ForEach()は働きがいろいろ分散しています。上の2つの足し算・掛け算メソッドは別物です。それらを単に action で統合しているだけです。

たとえば、テレビとビデオが一体化したやつがありますね。あれは便利なんですが、どっちかが壊れると両方使えません。ですが、別々に存在しつつもケーブルで統合しておけば、故障の原因もすぐに特定できます。

私はForEach()を使えるんですが、いまいち実務経験が少ないので、この程度のメリットしか見出せません。ですが、C#2.0で新たにできたということは、けっこうすごいことなんだと思いますね。

まとめ

ForEach()とActionは、注意すべきことがあります。メソッドの引数の渡し方です。

上のサンプルで i という変数に1,3,5,7,9が次々と渡されるわけですが、その渡し方は必ずしも明示的ではありません。new Action(~) でメソッドが追加されたときにはじまり、いつの間にかパラパラとiにわたっていくのです。

私も最初は戸惑いました。えっ? 渡してないのに!! みたいな。これは非常に不思議な機能です。渡さなくても勝手に渡される。

foreachキーワードを使う場合は、明示的に渡しますね。ここは注意しなければなりません。戸惑うかもしれませんが、あってます。

匿名メソッド?

delegateキーワードを使って、「匿名メソッド」というC#2.0の新機能を使うこともできます。デリゲート(Actionのこと)が難しいと言う場合は、むしろこっちを使った方が簡単かもしれません。

using System;
using System.Collections.Generic;
using System.Text;

namespace GenericsVersion
{
    class Program
    {
        private static void Main(string[] args)
        {
            int T_amount = 0;
            int K_amount = 1;

            List<int> li = new List<int>();
            li.Add(1);
            li.Add(3);
            li.Add(5);
            li.Add(7);
            li.Add(9);

            li.ForEach(delegate(int i) { 
                T_amount = T_amount + i;
                K_amount = K_amount * i; 
            });

            Console.WriteLine("足し算=" + T_amount);
            Console.WriteLine("掛け算=" + K_amount);
        }

    }
}

delegate(...){~; ~; }というのが、匿名メソッドです。不思議ですね。けど、さっきのコードよりもキレイにかけます。

こっちがいいや、と思う方はぜひ匿名メソッドを使ってください。こんなんメソッドじゃないよ、という方はActionを使ってください。

コードとしては匿名メソッドを使った方がきれいだと思いますから、できれば理解してくださいね。


[ ステップアップC# ]