2

これは些細なことのように思えるかもしれませんが、以下の 2 つの例について私は混乱しています。

int[] numbers = new int[] { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
int i = 0;

var simpleQuery =
    from num in numbers
    select ++i;

foreach (var item in simpleQuery)
{
    Console.WriteLine("v = {0}, i = {1}", item, i); // now i is incremented          
}

出力:

v = 1, i = 1
v = 2, i = 2
v = 3, i = 3
v = 4, i = 4
v = 5, i = 5
v = 6, i = 6
v = 7, i = 7
v = 8, i = 8
v = 9, i = 9
v = 10, i = 10

i の値を更新し、この時点まではすべて問題ありません。しかし、配列の要素を更新しようとすると、うまくいきません。

int[] numbers = new int[] { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
var simpleQuery =
    from num in numbers
    select ++num;

int i = 0;
foreach (var item in simpleQuery)
{
    Console.WriteLine("v = {0}, num = {1}", item, numbers[i++]); // now num is NOT incremented???
}

出力:

v = 6, num = 5
v = 5, num = 4
v = 2, num = 1
v = 4, num = 3
v = 10, num = 9
v = 9, num = 8
v = 7, num = 6
v = 8, num = 7
v = 3, num = 2
v = 1, num = 0

この背後にある理由は何でしょうか?

編集: 2番目の例は次のように出力されると思いました:

v = 6, num = 6
v = 5, num = 5
v = 2, num = 2
v = 4, num = 4
v = 10, num = 10
v = 9, num = 9
v = 7, num = 7
v = 8, num = 8
v = 3, num = 3
v = 1, num = 1
4

3 に答える 3

3

あなたのクエリは実際には次のようになります

numbers.Select(num => ++num)

実際には拡張メソッドの呼び出しです

Enumerable.Select(numbers, new Func<int, int>(num => ++num))

このメソッドは、配列の各項目に対してセレクターを実行します。Selector は無名関数です (つまり、この関数の名前はコンパイラによって生成されます)。そして、各アイテムがその関数に渡されます。配列内の項目が変更されない理由は次のとおりです。整数は値型です。値の型は値によって渡されます (つまり、アイテムへの参照を渡す代わりに、アイテムのコピーが作成されます)。そのため、内側のセレクターのコピーが変更されて返されます。これは、配列内の元のアイテムには影響しません。

i最初のケースでは、デリゲートで変数への参照をキャプチャしたため、次のようiに変更されました。

Enumerable.Select(numbers, new Func<int, int>(num => ++i))

配列アイテムはここでもメソッド引数として渡されますがi、メソッド本体に取り込まれます。実際には、そのような変数 (メソッド本体の一部) はコンパイラによって置き換えられます。最初のケースでは、変数iはクラスのフィールドに移動され、その変数へのすべての呼び出しはクラス フィールドへの呼び出しに置き換えられます。つまり、最初のケースでは、コンパイルされたコードは次のようになります。

Foo foo = new Foo();
foreach(int num in numbers)
   Console.WriteLine(foo.Bar(num));

Fooは生成されたネストされたクラスで、はBarそのクラスで生成されたメソッドであるセレクタ デリゲートです。

private class Foo
{
   private int _i; // variable is captured by delegate

   public int Bar(int x)
   {
       _i = _i + 1; // thats why it has new value on next call
       return _i;
   }
}
于 2013-07-02T07:05:23.883 に答える
1

最初のクエリはi(クロージャーの一部としてキャプチャされた) への参照を操作していますが、2 番目のクエリは参照ではなく、各配列要素のコピーを操作しています。LINQ クエリに渡される式はラムダ式にマップされ、ラムダ式に入力される変数 (num例のように) は値の型のコピーであり、参照ではありません。

于 2013-07-02T07:21:11.827 に答える