2012-07-17 104 views
0

可能重复:
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

末注释中有一条评论通过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()的情况。两者都会在这种情况下运行,还是只有没有评论的那个?

+1

这是从字面上[问18分钟前](http://stackoverflow.com/questions/11524532/delegate-method-inside-foreach-loop-always-binds-to-last-item)。 – 2012-07-17 14:40:16

+0

@KirkWoll 那么,它不是我想出来的,并没有搜查,我发现它在MSDN上,并不明白,因此在这里问 – Razort4x 2012-07-17 14:41:49

回答

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++; }; 
} 
3

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

如果呼叫是在循环内进行的,如您在下面的注释中所建议的那样,那么将在循环前进时评估i的值,并且将具有“正确”值。

+0

是的,我认为这是一个错字,并说即使它是不。那么让我们暂时考虑它是一个错字,并且让'f()'的调用在'if'子句中,那么是什么?此外,我已更新问题以反映相同。 – Razort4x 2012-07-17 15:01:25

0

这由(不幸)的设计只是。当您在代理中使用循环变量i时,它将以此方式捕获,并且当您到达f()呼叫时,i将具有其最终价值。

根据Eric Lippert的this blog post而不是for变量(这是您的示例中的变量),它将很快更改为foreach变量。

增加:

下面是与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); }在这种情况下,这相当于() => { Console.WriteLine(str); }

+0

这并非完全不幸的设计。闭包是一个非常强大的概念,并且支持一些非常酷的函数式编程技巧。多年来,逐引用语义已经存在于其他语言(lisp,scheme)中。相反,不幸的是有多少人误解了这个概念。 – cdhowie 2012-07-17 14:42:58

+0

@cdhowie是的,但看到我上面的编辑。现在有一个链接到Lippert的博客,他解释了这个“不幸”的故事。 – 2012-07-17 14:46:42

+0

嗯。我想我对闭合很熟悉,期待这种行为,所以它对我来说似乎并不是不幸。 – cdhowie 2012-07-17 14:52:36

相关问题