Значения в выражениях LINQ передаются по ссылке? - PullRequest
3 голосов
/ 22 сентября 2010

Я читаю книгу Мэннинга о LINQ, и есть пример:

    static class QueryReuse
    {
       static double Square(double n)
       {
         Console.WriteLine("Computing Square("+n+")...");
         return Math.Pow(n, 2);
       }
       public static void Main()
       {
         int[] numbers = {1, 2, 3};
         var query =
                  from n in numbers
                  select Square(n);

         foreach (var n in query)
              Console.WriteLine(n);

         for (int i = 0; i < numbers.Length; i++)
              numbers[i] = numbers[i]+10;

         Console.WriteLine("- Collection updated -");

         foreach (var n in query)
             Console.WriteLine(n);
    }
}

со следующим выводом:

Computing Square(1)...
1
Computing Square(2)...
4
Computing Square(3)...
9
- Collection updated -
Computing Square(11)...
121
Computing Square(12)...
144
Computing Square(13)...
169

Значит ли это, что «числа» передаются по ссылке? Это поведение должно что-то делать с ленивым исполнением и доходностью? Или я здесь не на том пути?

Ответы [ 5 ]

7 голосов
/ 22 сентября 2010

Это связано с ленивым казнью. Каждый раз, когда вы повторяете запрос, он снова будет смотреть на numbers. В самом деле, если вы измените значение позднего элемента numbers , а вы выполняете запрос, вы также увидите это изменение. Все это меняет содержимое массива.

Обратите внимание, что запрос запоминает значение numbers во время создания запроса, но это значение является ссылкой, а не содержимое массива. Поэтому, если вы измените само значение numbers следующим образом:

numbers = new int[] { 10, 9, 8, 7 };

тогда это изменение не будет отражено в запросе.

Просто для усложнения вещей, если вы используете переменные в других частях запроса, например:

int x = 3;

var query = from n in numbers
            where n == x
            select Square(n);

затем фиксируется переменная x, а не ее значение ... поэтому изменение x изменит результаты оценки запроса. Это потому, что выражение запроса действительно переводится в:

var query = numbers.Where(n => n == x)
                   .Select(n => Square(n));

Обратите внимание, что здесь, x используется в лямбда-выражении, но numbers нет - поэтому они ведут себя немного по-другому.

6 голосов
/ 22 сентября 2010

Ссылка на numbers передается значением .Тем не менее, запрос обрабатывается лениво, а базовый массив является изменяемым.

Так что это значит?

var arr = new[]{1,2,3,};
var q = arr.Select(i=>i*2);
Console.WriteLine(string.Join(", ",q.ToArray())); //prints 2, 4, 6
arr[0]=-1;
Console.WriteLine(string.Join(", ",q.ToArray())); //prints -2, 4, 6
// q refers to the original array, but that array has changed.
arr = new[]{2,3,4};
Console.WriteLine(string.Join(", ",q.ToArray())); //prints -2, 4, 6
//since q still refers to the original array, not the variable arr!

Как правило, это может сбить с толку довольно быстро, если вы измените переменные, а неих базовые объекты, поэтому лучше избегать таких изменений.

Например:

var arr = new[]{1,2,};
var arr2 = new[]{1,2,};
var q = from a in arr
        from b in arr2
        select a*b;

// q is 1,2,2,4
arr = new[]{0,1}; //irrelevant, arr's reference was passed by value
// q is still 1,2,2,4

arr2 = new[]{0,1}; //unfortunately, relevant
// q is now 0, 1, 0, 2

Чтобы понять это, вам нужно понять детали процесса компиляции.Выражения запроса определяются как эквивалент синтаксиса метода расширения (arr.Select...), который интенсивно использует замыкания.В результате фактически только первая перечислимая или запрашиваемая ссылка имеет свою ссылку, переданную по значению, остальные захватываются в замыканиях, и это означает, что их ссылки эффективно передаются по ссылке.Смущены еще? Избегайте изменения таких переменных, чтобы ваш код можно было обслуживать и читать.

3 голосов
/ 22 сентября 2010

Запрос сохраняется именно так - не набор результатов, а просто запрос.

Когда вы запрашиваете результаты из запроса, он оценивает запрос, используя текущие значения во время выполнения запроса, а не значения с момента создания запроса. Если вы оцениваете один и тот же запрос дважды, вы можете получить разные результаты, если базовые данные изменились, как показано в примере, приведенном в вопросе.

0 голосов
/ 22 сентября 2010

Это потому, что ссылка на numbers является замыканием и в сочетании с отложенным выполнением перечисления дает такой результат.

0 голосов
/ 22 сентября 2010

Да, переменная numbers передается по ссылке, не потому, что вы используете LINQ, а потому, что массивы являются ссылочными типами.

Тот факт, что выходные изменения происходят из-за отложенной / ленивой оценки LINQ.

...