外部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相同的方式。