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

时间:2010-09-22 09:10:48

标签: 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);
    }
}

使用以下输出:

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 个答案:

答案 0 :(得分:7)

这与懒惰执行有关。每次遍历查询时,都会再次查看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));

请注意,这里{1}}在lambda表达式中使用,但x不是 - 这就是为什么它们的行为略有不同。

答案 1 :(得分: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!

一般来说,如果更改变量而不是基础对象,它会很快变得混乱,因此最好避免这样的更改。

例如:

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...)等效。因此,实际上只有第一个可枚举或可查询的引用按值传递,其余的都在闭包中捕获,这意味着它们的引用通过引用有效传递。困惑了吗? 避免更改此类变量,以保持代码的可维护性和可读性。

答案 2 :(得分:3)

查询存储的确切 - 不是结果集,只是查询。

当您从查询中请求结果时,它会使用执行查询时的当前值来评估查询,而不是查询创建时的值。如果您对同一查询进行两次评估,则可以在基础数据发生更改时获得不同的结果,如您在问题中提供的示例所示。

答案 3 :(得分:0)

这是因为对numbers的引用是closure,并且结合了延迟执行它给出此结果的枚举。

答案 4 :(得分:-1)

是的,numbers变量是通过引用传递的,不是因为你使用LINQ,而是因为数组是引用类型。

输出更改的原因是LINQ的延迟/延迟评估。