2012-07-17 47 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

相關問題