2013-07-02 2 views
2

Это может показаться тривиальным для некоторых из вас, но я смущен этими двумя примерами ниже.LINQ над локальными переменными

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 

Что может быть причиной этого?

Edit: Я думал, что второй пример выведет:

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 
+0

Ваш код работает нормально. Что ты хочешь делать? 'select ++ i;' не связан ваш массив. Вы просто увеличиваете i для каждого элемента массива чисел –

+0

Запутанная часть состоит в том, что значение i изменяется из-за «select ++ i», но значение num не равно – sotn

ответ

3

Ваш запрос на самом деле выглядит

numbers.Select(num => ++num) 

Который на самом деле вызова метода расширения

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

Этот метод выполняет селектор в каждом элементе массива. Селектор - анонимная функция (т. Е. Имя для этой функции будет сгенерировано компилятором). И каждый элемент передается этой функции. И вот причина, почему элементы в массиве остаются неизменными - integer - тип значения. Типы значений передаются по значению (т. Е. Создается копия элемента вместо передачи ссылки на элемент). Таким образом, внутренняя копия селектора изменяется и возвращается. Это не влияет на исходный элемент в массиве.

В первом случае вы захватили ссылку на 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; 
    } 
} 
+1

Спасибо, сэр, это сделало вещи кристально чистыми – sotn

+1

@BerkArslan Я добавил образец, в котором более подробно объясняется, почему вы первый код работает. Думаю, будет полезно исследовать :) –

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

означает:

for(int i=0; i<numbers.Length; i++) 
{ 
    simpleQuery.Add(++i); 
} 

И

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

средства:

for(int i=0; i<numbers.Length; i++) 
{ 
    simpleQuery.Add(numbers[i] + 1); 
} 
+0

Итак, вы думаете, что ++ i изменит значение i, тогда как ++ num не изменит значение соответствующего элемента массива – sotn

+0

Что вы хотите сделать? Или вы просто попытаетесь понять, что ваш код работает –

+0

Во втором примере вы выбираете +1 каждого элемента. –

1

Ваш первый запрос работает на ссылку на i (захваченной в рамках закрытия), в то время как ваш второй запрос работает на копии каждого элемента массива, а не ссылки. Выражения, переданные в запросы LINQ, сопоставляются с лямбда-выражениями, а переменные, введенные в выражение лямбда (например, num в вашем примере), являются копиями для типов значений, а не ссылок.

Смежные вопросы