C#

C# 匿名メソッド(無名関数)

匿名メソッドは、別名「無名関数(英語:anonymous function、nameless function)」とも呼ばれる「名前付けされずに定義された関数」です。

 

名前がない関数をどうやって呼び出す?

匿名メソッドが「名前がない関数」ですが、関数に名前がないのにどうやってその関数を呼び出すのか、ということになります。
これについては、「デリゲート」が大きく関わってきます。

まずは、普通の名前があるメソッドを呼び出すデリゲートのプログラムです。

    delegate void DisplayString();  // delegateの宣言

    static void TestDisplay()
    {
        Console.WriteLine("テスト用のTestDisplayというメソッドです。");
    }

    static void Main(string[] args)
    {
        // デリゲートを生成する
        DisplayString func = new DisplayString(TestDisplay);
        // デリゲートの処理を実行
        func();

    }

 

このプログラムの場合、デリゲートで呼び出されるメソッドは”TestDisplay”という、メソッドの”名前”があります。

 

デリゲートは、メソッドを参照できる型で変数を作ることができます。
匿名メソッドは、メソッドの定義をそのままデリゲート変数に代入しておいて、そのデリゲートを呼び出すことで実行することができるというものです。

    delegate void DisplayString();  // delegateの宣言

    static void Main(string[] args)
    {
        // デリゲートに匿名メソッドを代入する
        DisplayString func = delegate () { Console.WriteLine("デリゲートですが、このメソッドそのものに名前はありません・・・"); };
        // デリゲートの処理を実行
        func();

    }

 

デリゲートを生成して実行し、”デリゲートですが、このメソッドそのものに名前はありません・・・”という文字列が表示されますが、このメソッドには本当に名前がありません(これが「匿名メソッド(無名関数)」と呼ばれるものです)。

では、下記のようなコードの場合どうなるでしょう。

 

テストプログラム1

delegate int ReturnInteger();  // delegateの宣言

static void Main(string[] args)
{
    ReturnInteger[] func = new ReturnInteger[10];
    for (int i = 0; i < 10; i++)
    {
        // デリゲートに匿名メソッドを代入する
        func[i] = delegate () { return i; };
    }
    for (int index = 0; index < 10; index++)
    {
        // デリゲートの処理を実行
        Console.WriteLine(func[index]());
    }
}

 

コードだけで見ると、想定される表示は、

0
1
2
3
4
5
6
7
8
9

ですが、実際にこのコードを実行すると

10
10
10
10
10
10
10
10
10
10

となります。

値がすべて同じなのも気になりますが、何より値がすべて10というのは不思議です。
(i=10だとforループの中の処理は実行されていないはずだからです)

ところが、このプログラムを下記のように変更するだけで想定した動きになります。

テストプログラム2

delegate int ReturnInteger();  // delegateの宣言

static void Main(string[] args)
{
    ReturnInteger[] func = new ReturnInteger[10];
    for (int i = 0; i < 10; i++)
    {
        int val = i;    // <-- ローカル変数'val'を用意して値を代入する
        // デリゲートに匿名メソッドを代入する
        func[i] = delegate () { return val; };  // returnで返る値を'i'から'val'に変える
    }
    for (int index = 0; index < 10; index++)
    {
        // デリゲートの処理を実行
        Console.WriteLine(func[index]());
    }
}

 

上記のコードを実行すると、

0
1
2
3
4
5
6
7
8
9

と、想定した通りの動きになります。

この2つのプログラムの違いは、ループ内のローカル変数に値を代入して、匿名メソッドはそのローカル変数を扱うように変更しただけです。

 

なぜ結果が違う?

まず、スコープ外に出たはずの変数の値がなぜ扱えているかというと、「匿名メソッドが上位スコープの変数を参照しているとき、その変数の寿命を匿名メソッドの寿命が尽きるまで延命する機能」で、「外部変数のキャプチャ」という機能によるものです。

テストプログラム1では、生成した10個の匿名メソッドが参照していた変数は’i’という変数1つです(しかも変数iはループを脱出したときの値になっているので、値はすべて’10’だったのです)。

テストプログラム2では、「iが0のときのvalという変数の値」、「iが1のときのvalという変数の値」・・・というように、10個の匿名メソッドそれぞれで個別の変数’val’が存在して生成されており、それぞれの値が保持されていたからです。

「匿名メソッド(無名関数)」を使うときには、この「外部変数のキャプチャ」に注意してお使いください。

 

タイトルとURLをコピーしました