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();
}
值得注意的是,这个陷阱也存在于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());
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将警告“Access to modified closure”,在这种情况下可以安全地忽略它。
5条答案
按热度按时间jtoj6r0c1#
当开发人员期望变量的值被lambda表达式或匿名委托捕获时,会发生“外部变量陷阱”,而实际上变量本身被捕获。
示例:
可能输出#1:
可能输出#2:
如果您期望输出#1,那么您已经落入了外部变量陷阱。您将得到输出#2。
修复:
声明一个要重复捕获的“内部变量”,而不是只捕获一次的“外部变量”。
有关更多详细信息,请参见Eric Lippert's blog。
brccelvz2#
就像
将不会给予您所期望的结果,因为Contains不是为每次迭代执行的。不过,在循环中将s赋值给临时变量可以解决这个问题。
8nuwlpux3#
值得注意的是,这个陷阱也存在于
foreach
循环中,但has been changed从C# 5.0开始就存在了,即在foreach
循环中,闭包现在每次都会关闭循环变量的新副本。下面的代码:打印
120 120 120
〈C# 5.0,但100 110 120
〉= C# 5.0但是
for
循环仍然以相同的方式运行。jjjwad0x4#
@dtb是正确的(big +1),但重要的是要注意,这只适用于闭包的范围扩展到循环之外的情况。例如:
这将打印:
ReSharper将警告“Access to modified closure”,在这种情况下可以安全地忽略它。
ttisahbt5#
wiki article解释闭包的概念是有帮助的。
此外,this article在更具体的C#实现中确实很好。
总之,TL;dr是变量作用域在匿名委托或lambda表达式中与在代码中的任何其他地方一样重要--行为并不那么明显。