0

重複の可能性:
C#:ラムダ式でforeachループのイテレーター変数を使用する-なぜ失敗するのですか?

私はMSDNでc#リファレンスを読んでいて、これを見つけました。

http://msdn.microsoft.com/en-us/library/0yw3tz5k.aspx

コメントの最後に1つのコメントがありますalbionmike それはこのようになります。

When you "catpure" a variable from an outer scope, some counter-intuitive things happen.
If you run this, you will get an IndexOutOfRange exception during the call f().
If you uncomment the two commented out lines of code, it will work as expected.
Hint: Captured Outer Variables have reference rather than value semantics

// Console Project
using System;
using System.Collections.Generic;
using System.Text;


namespace EvilDelegation
{
    delegate void PrintIt();

    class Program
    {

        static void Main(string[] args)
        {
            string[] strings = { "zero", "one", "two", "three", "four" };
            PrintIt f = null;
            for (int i = 0; i < strings.Length; ++i) {
                if (i == 2 || i == 3) {
                    // Can you see why this would not work?
                    f = delegate() { Console.WriteLine(strings[i]); };

                    // But this does...
                    //int k = i;
                    //f = delegate() { Console.WriteLine(strings[k]); };

                }
            }
            f();
        }
    }
}

わからない、なぜ最初のものが機能しないのか、そして2番目のものが機能するのか?4行目で、彼は次のように述べていますCaptured Outer Variables have reference rather than value semantics
じゃ、いいよ。しかし、forループでは、もちろん値型であると定義iしたintので、型はどのようintに参照を保持できますか?そして、i参照を保持できない場合、それは値を格納していることを意味し、値を格納している場合、最初のものが機能せず、2番目のものが機能する理由がわかりませんか?
ここで何かが足りませんか?

編集:元の作者にはタイプミスがあったと思います。f()の呼び出しはifループ内にあるはずです。答えるときはこれを考慮してください。

編集2:さて、誰かが言うかもしれない場合に備えて、それはタイプミスではなかった、それがあったと考えてみましょう。節f()の中で呼び出しが行われる場合を知りたい。ifその場合、両方が実行されますか、それともコメントされていない方だけが実行されますか?

4

3 に答える 3

4

これは、クロージャのセマンティクスによるものです。クロージャが外部スコープのローカル変数を参照する場合、変数に含まれる値ではなく、変数への参照をキャプチャします。

この場合、匿名デリゲートは変数への参照をdelegate() { Console.WriteLine(strings[i]); }キャプチャしています。つまり、変数は無名関数と宣言されたスコープの間で共有されます。一方のコンテキストで変更すると、もう一方のコンテキストでも変更されます。iii

例(実行を参照):

using System;

class Foo {
    static void Main() {
        int i = 0;
        Action increment = delegate { ++i; };

        Console.WriteLine(i);

        ++i;
        Console.WriteLine(i);

        increment();
        Console.WriteLine(i);

        ++i;
        Console.WriteLine(i);

        increment();
        Console.WriteLine(i);
    }
}

これは出力します:

0
1
2
3
4

C#では、ローカルの有効期間が延長され、それらを参照するクロージャの有効期間が含まれます。これにより、C /C++開発者の感性を損なう可能性のある非常に興味深いトリックが可能になります。

static Func<int> Counter() {
    int i = 0;
    return delegate { return i++; };
}
于 2012-07-17T14:39:47.227 に答える
3

変更されたクロージャーにアクセスしています。デリゲートは、呼び出され、ループ変数をキャプチャしたときにのみ評価されますi。これにアクセスするとき、ループが終了した後、その値はに等しくなりstrings.Lengthます。ただし、kループ内にローカル変数を導入するkことにより、ループのその反復の特定の変数をキャプチャし、結果は正しくなります。

以下のコメントで示唆されているように、呼び出しがループ内で行われた場合、の値はiループが進む前のその時点で評価され、「正しい」値になります。

于 2012-07-17T14:39:43.733 に答える
0

それは(残念なことに)設計によるものです。デリゲートでループ変数を使用するiと、そのようにキャプチャされi、呼び出しに到達したときに最終的な値になりますf()

Eric Lippertによるこのブログ投稿foreachによると、変数については間もなく変更されますが、変数については変更されません(これはあなたの例にあります)。for

添加:

foreach代わりにfor:を使用した例を次に示します。

  string[] strings = { "zero", "one", "two", "three", "four" }; 

  var list = new List<PrintIt>();

  foreach (var str in strings)
  {
    PrintIt f = delegate { Console.WriteLine(str); };  // captures str
    list.Add(f);
  }
  var f0 = list[0];
  f0();

私の.NETバージョン(.NET 4.0、C#4)では、最後の行に「4」と表示されます。今後の.NETバージョン(.NET 4.5、C#5、Visual Studio 2012)では、私が理解しているように、「ゼロ」と出力されます。重大な変更...

もちろん、この場合はと同等です。これはと同等delegate { Console.WriteLine(str); }です。delegate() { Console.WriteLine(str); }() => { Console.WriteLine(str); }

于 2012-07-17T14:41:58.270 に答える