是什么决定变量是否被关闭?

时间:2013-08-03 03:50:00

标签: c#

//var sample= new { bottom=2, top=5,count=4};
var sample= new [] {2,3,4,5,6};
var q = from x in new int[]{0} //force desired behavior
    from i in sample
    from j in sample
    select Math.Pow(i,j);


q.Distinct().Count().Dump();
sample = new[]{2,3,4,5};
//q = from i in Enumerable.Range(sample.bottom, sample.top-sample.bottom+1)
//  from j in Enumerable.Range(sample.bottom, sample.top-sample.bottom+1)
//  select checked(System.Numerics.BigInteger.Pow((BigInteger)i,j));
q.Distinct().Count().Dump();

如果from x line不存在,或者q变量未重置,则每次第二个答案都不正确。(此处显示已注释掉)

原始样本是一个匿名变量,但阵列也是这样做的。

var sample = new { bottom =2, top=5};

是否与此Scope of Linq Expressions defined in a loop - At issue: closing over loop variable相关?

为什么在顶部放置一个1项数组来修复闭包?

2 个答案:

答案 0 :(得分:2)

此代码两次关闭sample

from x in new int[]{0}
from i in sample
from j in sample
select Math.Pow(i,j);

实际方法调用此计算结果为:

new int[] {0}
    .SelectMany(x => sample, (x, i) => new {x, i})
    .SelectMany(@t => sample, (@t, j) => Math.Pow(@t.i, j));

此代码一次关闭sample

from i in sample
from j in sample
select Math.Pow(i,j);

实际方法调用此计算结果为:

sample.SelectMany(i => sample, (i, j) => Math.Pow(i, j));

因此,如果您更改sample,则只有i => sample lambda会“看到”sample的“新”值; sample.SelectMany已经使用“旧”值运行。

这两个示例都会导致R#发出访问修改后的闭包警告,这通常是一个大红旗。考虑为每个查询保留sample的私有副本,或者为您执行一个函数参数:

IEnumerable<int> SelectManyPow(int[] sample)
{
    return from i in sample
           from j in sample
           select Math.Pow(i, j);
}

答案 1 :(得分:2)

理解这里发生的事情的第一步是了解如何翻译查询表达式。根据规范:

from x1 in e1
from x2 in e2
select v

被翻译成

e1.SelectMany(x1 => e2, (x1, x2) => v)

理解非常非常重要。 from x1 in e1 from x2 in e2 select v 的结果是对e1的查询,无论e1是什么;它是已修复,期间。

现在,这是问题所在。我们来看看你的问题。

var q = from i in sample
        from j in sample
        select Math.Pow(i, j)

构造查询时sample at的值是对数组创建表达式new[] { 2, 3, 4, 5, 6 }的结果的引用。设e1为对该数组创建表达式结果的引用。然后查询变为:

e1.SelectMany(i => sample, (i, j) => Math.Pow(i, j));

这是已修复,句号,无论您重新分配给sample。也就是说, 想到

var sample = new[] { 2, 3, 4, 5, 6 }
var q = from i in sample
        from j in sample
        select Math.Pow(i, j)

作为

var sample = new[] { 2, 3, 4, 5, 6 };
var e1 = sample;
var q = e1.SelectMany(i => sample, (i, j) => Math.Pow(i, j));

现在,sample 已关闭,这就是之后为sample分配新值的原因,您会看到不同的查询结果。但是无论e1仍然是数组创建表达式new[] { 2, 3, 4, 5, 6 }的结果。

但是,当您从查询开始

var q = from x in new int[] { 0 }
        from i in sample
        from j in sample
        select Math.Pow(i, j)

现在该查询被翻译为

int[] e1 = new int[] { 0 };
var q = e1.SelectMany(
    x1 => sample.SelectMany(i => sample, (i, j) => Math.Pow(i, j)
);

现在 sample被捕获为i j的序列,但e1已修复