【问题标题】:Why does not this delegate work inside the loop? [duplicate]为什么这个委托不在循环内工作? [复制]
【发布时间】:2012-07-16 12:21:51
【问题描述】:

可能重复:
C#: using the iterator variable of foreach loop in a lambda expression - why fails?

我在 MSDN 上阅读 c# 参考资料,我发现了这个..

http://msdn.microsoft.com/en-us/library/0yw3tz5k.aspx

在 cmets 的末尾有一条albionmike 的评论 是这样的。。

When you "catpure" a variable from an outer scope, some counter-intuitive things happen.
If you run this, you will get an IndexOutOfRange exception during the call f().
If you uncomment the two commented out lines of code, it will work as expected.
Hint: Captured Outer Variables have reference rather than value semantics

// Console Project
using System;
using System.Collections.Generic;
using System.Text;


namespace EvilDelegation
{
    delegate void PrintIt();

    class Program
    {

        static void Main(string[] args)
        {
            string[] strings = { "zero", "one", "two", "three", "four" };
            PrintIt f = null;
            for (int i = 0; i < strings.Length; ++i) {
                if (i == 2 || i == 3) {
                    // Can you see why this would not work?
                    f = delegate() { Console.WriteLine(strings[i]); };

                    // But this does...
                    //int k = i;
                    //f = delegate() { Console.WriteLine(strings[k]); };

                }
            }
            f();
        }
    }
}

我不明白,为什么第一个不行,第二个不行?在第四行,他说:Captured Outer Variables have reference rather than value semantics
好的。但是在for循环中,我们将i定义为int,当然是值类型,那么int类型怎么能持有引用呢?如果i 不能保存引用,这意味着它正在存储价值,如果它正在存储价值,那么我不明白为什么第一个不起作用而第二个会起作用?
我在这里遗漏了什么吗?

编辑:我认为原作者有一个错字,对 f() 的调用应该在 if 循环中。请在回答时考虑这一点。

编辑 2: 好吧,如果有人可能会说,这不是错字,让我们考虑一下。我想知道在if 子句中调用f() 的情况。在那种情况下都会运行,还是只运行一个没有评论的?

【问题讨论】:

  • 这实际上是asked 18 minutes ago
  • @KirkWoll 嗯,不是我自己想出来的,没去搜,是在MSDN上找到的,看不懂,所以在这里问

标签: c# generics delegates


【解决方案1】:

这只是(不幸的)设计。当您在委托中使用循环变量 i 时,它会像这样被捕获,当您到达 f() 调用时,i 将具有其最终值。

根据 Eric Lippert 的 this blog post 的说法,foreach 变量很快就会发生变化,但 for 变量不会(这就是您在示例中所拥有的)。

加法:

这是一个使用foreach 而不是for 的示例:

  string[] strings = { "zero", "one", "two", "three", "four" }; 

  var list = new List<PrintIt>();

  foreach (var str in strings)
  {
    PrintIt f = delegate { Console.WriteLine(str); };  // captures str
    list.Add(f);
  }
  var f0 = list[0];
  f0();

在我的 .NET 版本(.NET 4.0,C# 4)中,最后一行打印“四”。据我了解,在即将推出的 .NET 版本(.NET 4.5、C# 5、Visual Studio 2012)中,它将打印“零”。 重大变化...

当然,delegate { Console.WriteLine(str); } 等价于 delegate() { Console.WriteLine(str); } 在这种情况下相当于() =&gt; { Console.WriteLine(str); }

【讨论】:

  • 这绝不是不幸的设计。闭包是一个非常强大的概念,可以实现一些非常酷的函数式编程技巧。引用捕获语义在其他语言(lisp、scheme)中已经存在多年了。不幸的是,有多少人误解了这个概念。
  • @cdhowie 是的,但请参阅我上面的编辑。现在有一个指向 Lippert 的博客的链接,他在其中解释了“不幸”的故事。
  • 嗯。我想我对闭包已经足够熟悉了,可以预料到这种行为,所以它对我来说一点也不不幸。
【解决方案2】:

您正在访问修改后的闭包。你的委托只会在它被调用时被评估,并且它已经捕获了循环变量i。在访问它时,在循环退出后,它的值将等于strings.Length。但是,通过在循环中引入局部变量 k,您将捕获循环迭代的特定变量 k,并且结果是正确的。

如果调用是在循环内进行的,正如您在下面的评论中所建议的那样,那么 i 的值将在循环前进之前在该点进行评估,并且将具有“正确”的值。

【讨论】:

  • 是的,我认为这是一个错字,即使不是。然后让我们暂时认为这是一个错字,假设对f() 的调用在if 子句中,然后呢?另外,我已经更新了问题以反映相同的情况。
【解决方案3】:

这是因为闭包的语义。当闭包在其外部范围内引用局部变量时,它会捕获对变量的引用,而不是变量中包含的值。

在这种情况下,匿名委托delegate() { Console.WriteLine(strings[i]); } 正在捕获对i 变量的引用;也就是说,该变量在匿名函数和声明i 的范围之间共享。当i 在一个上下文中发生变化时,它也会在另一个上下文中发生变化。

例如(see it run):

using System;

class Foo {
    static void Main() {
        int i = 0;
        Action increment = delegate { ++i; };

        Console.WriteLine(i);

        ++i;
        Console.WriteLine(i);

        increment();
        Console.WriteLine(i);

        ++i;
        Console.WriteLine(i);

        increment();
        Console.WriteLine(i);
    }
}

这将输出:

0
1
2
3
4

在 C# 中,局部变量的生命周期被扩展为包括引用它们的任何闭包的生命周期。这使得一些非常有趣的技巧可能会冒犯 C/C++ 开发人员的敏感性:

static Func<int> Counter() {
    int i = 0;
    return delegate { return i++; };
}

【讨论】:

    猜你喜欢
    • 2014-04-25
    • 1970-01-01
    • 2010-10-17
    • 1970-01-01
    • 1970-01-01
    • 2016-03-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多