【问题标题】:Outer Variable Trap外部变量陷阱
【发布时间】:2010-08-05 16:08:48
【问题描述】:

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

编辑:结合 Jon Skeet 的命令 :)

Eric Lippert on the Outer Variable Trap

【问题讨论】:

  • 在我用谷歌搜索之前,我不知道你在说什么;在此过程中,我发现了大量的解释和示例(在 C# 中),那么您还需要什么?
  • @Marc 也许 OP 是那些希望 SO 对每个可能的相关编程问题都有答案的人之一(肯定有更多,至少有人明确说过很多次)。这个答案显然丢失了。
  • @Maciej,非常棒。主名单离完成又近了一步!网络统治,我们来了!
  • @Maciej 谢谢!那我想从顶级专家那里得到最好的答案,而不是汤姆、迪克或哈利!我还能在哪里找到一流的作家和思想家...以最好的方式回答我
  • 到 OP。也许我只是有一个“今天是个混蛋”,但我忍不住要指出这一点,对不起。也许当我获得编辑能力时,我就不再是个混蛋了。话题就是它,一个话题。它的目的是告诉读者问题是关于什么的。如果问题简短且完全符合主题,那很好,但在实际问题的地方应该有一个问题。

标签: c# linq


【解决方案1】:

当开发人员期望变量的值被 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

【讨论】:

  • “陷阱”一词有趣的双关语:变量被捕获(陷入),而你被问题抓住(陷入陷阱)
  • 鉴于这可能会成为公认的答案,您是否有机会添加指向 Eric Lippert 博客文章的链接? blogs.msdn.com/b/ericlippert/archive/2009/11/12/…
  • 所以 j 基本上是一个“新鲜”变量,因此第 k 个动作包含一个变量 j_k 绑定到循环变量假定的第 k 个值。结果得到了预期的行为。
【解决方案2】:

类似

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

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

【讨论】:

  • 我不熟悉var = 语法,它有什么作用? =P
  • Nitpick:Contains 每次迭代都会执行,但令人惊讶的是,s 总是具有相同的值。
  • @dtb @Marc 是的,dtb 说的。我可能没有解释得那么好。 var 只是显式声明返回类型的一种替代方法(在实际代码中,我实际上会在此处声明返回类型,但我不在 IDE 前面,也不想查找实际返回的位置...我认为它的 IEnumberable)
【解决方案3】:

值得注意的是,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 ,但100 110 120 >= C# 5.0

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

【讨论】:

    【解决方案4】:

    @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 将警告“访问修改后的闭包”,在这种情况下可以安全地忽略它。

    【讨论】:

      【解决方案5】:

      这篇解释闭包概念的文章很有帮助:

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

      另外,这篇文章从更具体的 C# 实现来看确实很棒:

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

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-09-14
        相关资源
        最近更新 更多