我正在阅读关于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
这是否意味着'数字'是通过引用传递的?这种行为是否必须对延迟执行和收益做些什么?或者我在这里错了?
答案 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的延迟/延迟评估。