3

2 つのコード ブロックの違いがはっきりとわかりません。プログラムがあると考える

    class Program
{
    static void Main(string[] args)
    {

        List<Number> numbers = new List<Number>
                                   {
                                       new Number(1),
                                       new Number(2),
                                       new Number(3)
                                   };


        List<Action> actions = new List<Action>();
        foreach (Number numb in numbers)
        {
            actions.Add(() => WriteNumber(numb));
        }

        Number number = null;
        IEnumerator<Number> enumerator = numbers.GetEnumerator();
        while (enumerator.MoveNext())
        {
            number = enumerator.Current;
            actions.Add(() => WriteNumber(number));
        }

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

        Console.ReadKey();


    }

    public static void WriteNumber(Number num)
    {
        Console.WriteLine(num.Value);
    }

    public class Number
    {
        public int Value;

        public Number(int i)
        {
            this.Value = i;
        }

    }

}

出力は

1
2
3
3
3
3    

これら 2 つのコード ブロックは同じように動作するはずです。しかし、最初のループではクロージャが機能していないことがわかります。私は何が欠けていますか?

前もって感謝します。

4

4 に答える 4

3

numberwhile ループの外で変数を宣言します。数値ごとに、その参照を変数に保存しますnumber-最後の値を上書きするたびに。

宣言を while ループ内に移動するだけで、数値ごとに新しい変数が作成されます。

    IEnumerator<Number> enumerator = numbers.GetEnumerator();
    while (enumerator.MoveNext())
    {
        Number number = enumerator.Current;
        actions.Add(() => WriteNumber(number));
    }
于 2013-10-03T14:26:00.297 に答える
3

これら 2 つのコード ブロックは同じように動作するはずです。

いいえ、そうすべきではありません - 少なくとも C# 5 では。C# 3 および 4では、実際にはそうなります。

しかし、foreachC# 5 のループでは、ループの反復ごとに 1 つの変数があります。ラムダ式はその変数をキャプチャします。ループの後続の反復では、以前にキャプチャされた変数に影響を与えないさまざまな変数が作成されます。

whileループには、すべての反復でキャプチャされる変数が1ありますその変数への変更は、それをキャプチャしたすべてのデリゲートに表示されます。これは、ループの後に次の行を追加することで確認できます。while

number = new Number(999);

次に、出力は次のようになります

1
2 
3
999
999
999

現在、C# 3 および 4 では、foreach仕様は基本的に設計上破られていました。つまり、すべての反復で 1 つの変数をキャプチャしていました。これは C# 5 で修正され、反復ごとに個別の変数を使用するようになりました。これは基本的に、この種のコードで常に必要とされるものです。

于 2013-10-03T14:26:05.120 に答える
1

あなたのループで:

    Number number = null;
    IEnumerator<Number> enumerator = numbers.GetEnumerator();
    while (enumerator.MoveNext())
    {
        number = enumerator.Current;
        actions.Add(() => WriteNumber(number));
    }

number がループ スコープの外で宣言されています。したがって、次の現在のイテレータに設定されると、number へのすべてのアクション参照も最新のものに更新されます。したがって、各アクションを実行すると、すべて最後の番号が使用されます。

于 2013-10-03T14:28:02.670 に答える
0

ご回答ありがとうございます。しかし、私は誤解されていたと思います。私は仕事を終わらせたいです。そのため、ループ変数を範囲外に設定しました。問題は、最初のケースで機能しないのはなぜですか? C# 3.5 (C# 5.0 ではない) を使用していることを忘れていました。したがって、soop 変数はスコープ外で定義する必要があり、2 つのコード ブロックは同じように機能する必要があります。

于 2013-10-04T09:43:34.580 に答える