5

私は頭を動かそうとしていますが、ここで何が起こりますか? コンパイラはどのようなコードを生成しますか?

public static void vc()
{
    var listActions = new List<Action>();

    foreach (int i in Enumerable.Range(1, 10))
    {
        listActions.Add(() => Console.WriteLine(i));
    }

    foreach (Action action in listActions)
    {
        action();
    }
}

static void Main(string[] args)
{ 
  vc();
}

出力: 10 10 .. 10

thisによると、繰り返しごとに ActionHelper の新しいインスタンスが作成されます。したがって、その場合、1..10 を出力する必要があると思います。コンパイラがここで行っていることの疑似コードを教えてもらえますか?

ありがとう。

4

3 に答える 3

10

この行で

 listActions.Add(() => Console.WriteLine(i));

変数iがキャプチャされるか、必要に応じて、その変数のメモリ位置へのポインターが作成されます。これは、すべてのデリゲートがそのメモリ位置へのポインターを取得したことを意味します。このループの実行後:

foreach (int i in Enumerable.Range(1, 10))
{
    listActions.Add(() => Console.WriteLine(i));
}

(s) に存在するすべてのポインタが指しているメモリの内容は 10 になりiます10Action

つまり、iキャプチャされます。

ところで、Eric Lippert によると、この「奇妙な」動作は で 解決されることに注意してくださいC# 5.0

したがって、C# 5.0プログラムでは期待どおりに出力されます。

1,2,3,4,5...10

編集:

件名に関する Eric Lippert の投稿は見つかりませんが、別の投稿を次に示します。

ループ内閉鎖の再検討

于 2012-10-12T14:50:34.220 に答える
9

コンパイラはどのようなコードを生成しますか?

あなたはこれを期待しているように聞こえます:

foreach (int temp in Enumerable.Range(1, 10))
{
    int i = temp;
    listActions.Add(() => Console.WriteLine(i));
}

これは反復ごとに異なる変数を使用するため、ラムダが作成された時点での変数の値が取得されます。実際、この正確なコードを使用して、必要な結果を得ることができます。

しかし、コンパイラが実際に行うことは、これに近いものです。

int i;
foreach (i in Enumerable.Range(1, 10))
{
    listActions.Add(() => Console.WriteLine(i));
}

これにより、反復ごとに同じ変数をキャプチャしていることがわかります。後でそのコードを実際に実行すると、それらはすべて、すでに 10 までインクリメントされている同じ値を参照します。

これはコンパイラやランタイムのバグではありません...言語設計チームの意識的な決定でした。ただし、これがどのように機能するかについて混乱しているため、彼らはその決定を覆し、C# 5 で期待されるように機能させるために破壊的変更の危険を冒すことにしました

于 2012-10-12T14:55:33.657 に答える
0

本質的に何が起こっているかというと、コンパイラはint iループの外側を宣言しているため、アクションごとに新しい値を作成しておらず、毎回同じ値を参照しているだけです。コードを実行するまでに i は 10 なので、10 がたくさん出力されます。

于 2012-10-12T14:51:13.793 に答える