LINQ表达式中的值是否通过引用传递?

kof*_*cii 3 linq pass-by-reference

我正在阅读关于LINQ的manning书,有一个例子:

    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);
    }
}
Run Code Online (Sandbox Code Playgroud)

具有以下输出:

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
Run Code Online (Sandbox Code Playgroud)

这是否意味着'数字'是通过引用传递的?这种行为是否必须对延迟执行和收益做些什么?或者我在这里走错了路?

Jon*_*eet 7

这与懒惰的执行有关.每次遍历查询时,都会numbers再次查看.实际上,如果在执行查询numbers 更改后期元素的值,您也会看到更改.这都是改变数组的内容.

请注意,查询会记住numbers查询创建时的值 - 但该值是引用,而不是数组的内容.所以,如果你numbers像这样改变自己的价值:

numbers = new int[] { 10, 9, 8, 7 };
Run Code Online (Sandbox Code Playgroud)

然后该更改将不会反映在查询中.

只是为了使事情复杂化,如果在查询的其他部分中使用变量,如下所示:

int x = 3;

var query = from n in numbers
            where n == x
            select Square(n);
Run Code Online (Sandbox Code Playgroud)

然后捕获变量 x而不是其值...因此更改x将更改评估查询的结果.那是因为查询表达式真的被翻译成:

var query = numbers.Where(n => n == x)
                   .Select(n => Square(n));
Run Code Online (Sandbox Code Playgroud)

请注意,这里x使用的是lambda表达式,但numbers不是 - 这就是为什么它们的行为略有不同.


Eam*_*nne 6

参考到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!
Run Code Online (Sandbox Code Playgroud)

通常,如果您更改变量而不是基础对象,它会很快变得混乱,因此最好避免这样的更改.

例如:

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
Run Code Online (Sandbox Code Playgroud)

要理解这一点,您需要了解编译过程的详细信息.查询表达式被定义为等效于扩展方法syntax(arr.Select...),它大量使用闭包.因此,实际上只有第一个可枚举或可查询的引用按值传递,其余的都在闭包中捕获,这意味着它们的引用通过引用有效传递.困惑了吗? 避免更改此类变量以保持代码的可维护性和可读性.