外部variables陷阱

什么是外部variables陷阱? 在C#的解释和例子,赞赏。

编辑:合并Jon Skeet的diktat 🙂

埃里克Lippert在外部可变的陷井

当一个开发者希望一个variables的值被一个lambdaexpression式或匿名委托捕获时,就会出现“外部variables陷阱”。

例:

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,你已经陷入了外部variables陷阱。 你得到输出#2。

固定:

声明一个“内部variables”被重复捕获,而不是只捕获一次的“外部variables”。

 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的博客 。

就像是

 foreach (var s in strings) var x = results.Where(r => (r.Text).Contains(s)); 

不会给出你所期望的结果,因为每个迭代都不执行Contains。 虽然将s分配给循环内部的临时variables将会解决这个问题。

@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); } } 

这将打印:

 我:1匹配:比尔
我:5匹配:鲍勃
我:9匹配:大卫 

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

这篇文章解释closures的概念是有帮助的:

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

另外,从一个更具体的C#实现中,这篇文章是非常好的:

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

无论如何,tl; lr是variables作用域在匿名委托或lambdaexpression式中与在代码中的其他任何地方一样重要 – 行为就不那么明显了。

值得注意的是,这个陷阱也存在于foreach循环中,但自从C#5.0以来, 已经发生了变化 ,即在每次循环variables的新副本中closuresforeach循环。 所以下面的代码:

 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 <C#5.0 ,但100 110 120 > = C#5.0

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