2010-09-22 65 views
3

我读的书曼宁有关LINQ,有一个例子: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 

这是否意味着,“数字”是按引用传递?这种行为是否需要执行懒惰的执行和收益?或者我在这里错了吗?

回答

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将改变评估查询的结果。这是因为查询表达式真的翻译成:在这里,x是lambda表达式中使用

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

注意,但numbers不是 - 这就是为什么他们的表现略有不同。

-1

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

输出变化的事实是由于LINQ的推迟/懒惰评估。

+1

“按引用传递”和“按值传递引用”有所不同。请参阅http://pobox.com/~skeet/csharp/parameters.html – 2010-09-22 09:16:47

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

3

查询的存储方式完全相同 - 不是结果集,只是查询。

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

0

这是因为对numbers的引用是closure,并且结合懒惰执行的枚举给出了这个结果。