外变量陷阱

时间:2010-08-05 16:08:49

标签: c# linq

外部变量陷阱究竟是什么? 感谢C#中的解释和示例。

编辑:纳入Jon Skeet的diktat :)

Eric Lippert on the Outer Variable Trap

5 个答案:

答案 0 :(得分:65)

当开发人员期望变量的值被lambda表达式或匿名委托捕获时,实际上是自己捕获变量时,会发生“外部变量陷阱”。

示例:

var actions = new List<Action>();
for (var i = 0; i < 10; i++)
{
    actions.Add(() => Console.Write("{0} ", i));
}
foreach (var action in actions)
{
    action();
}

可能的输出#1:

0 1 2 3 4 5 6 7 8 9

可能的输出#2:

10 10 10 10 10 10 10 10 10 10

如果你期望输出#1,你就陷入了外部变量陷阱。你得到输出#2。

<强>修正:

声明要重复捕获的“内部变量”,而不是仅捕获一次的“外部变量”。

var actions = new List<Action>();
for (var i = 0; i < 10; i++)
{
    var j = i;
    actions.Add(() => Console.Write("{0} ", j));
}
foreach (var action in actions)
{
    action();
}

有关详情,请参阅Eric Lippert's blog

答案 1 :(得分:4)

这样的东西
foreach (var s in strings)
    var x = results.Where(r => (r.Text).Contains(s));

不会给出您期望的结果,因为每次迭代都不会执行Contains。但是,将s分配给循环内的临时变量将解决这个问题。

答案 2 :(得分:1)

@dtb是正确的(大+1),但重要的是要注意,这只适用于闭包的范围扩展到循环之外的情况。例如:

var objects = new []
    {
        new { Name = "Bill", Id = 1 },
        new { Name = "Bob", Id = 5 },
        new { Name = "David", Id = 9 }
    };

for (var i = 0; i < 10; i++)
{
    var match = objects.SingleOrDefault(x => x.Id == i);

    if (match != null)
    {
        Console.WriteLine("i: {0}  match: {1}", i, match.Name);
    }
}

这将打印:

i: 1  match: Bill
i: 5  match: Bob
i: 9  match: David

ReSharper会警告“访问修改后的闭包”,在这种情况下可以安全地忽略。

答案 3 :(得分:1)

值得注意的是,这个陷阱也存在于foreach循环中,但是has been changed因为C#5.0,即内部foreach循环闭包现在每次关闭一个新的循环变量副本。所以下面的代码:

var values = new List<int>() { 100, 110, 120 };
var funcs = new List<Func<int>>();
foreach (var v in values)
    funcs.Add(() => v);
foreach (var f in funcs)
    Console.WriteLine(f());

打印120 120 120 &lt; C#5.0 ,但100 110 120 &gt; = C#5.0

但是for循环的行为方式仍然相同。

答案 4 :(得分:0)

本文解释闭包的概念很有帮助:

http://en.wikipedia.org/wiki/Closure_(computer_science)

此外,从更具体的C#实现开始,本文非常好:

http://blogs.msdn.com/b/abhinaba/archive/2005/08/08/448939.aspx

无论如何,tl; lr是变量范围在匿名委托或lambda表达式中与在代码中的任何其他位置一样重要 - 行为不是那么明显。