2013-02-16 67 views
82

我得到以下警告:訪問的foreach變量封警告

訪問的foreach在封閉的可變。使用不同版本的編譯器編譯時可能會有不同的行爲。

這就是它看起來像在我的編輯器:

abovementioned error message in a hover popup

我知道如何解決這個問題的警告,但我想知道爲什麼我會得到這樣的警告?

這是關於「CLR」版本嗎?它與「IL」有關嗎?

+4

http://stackoverflow.com/questions/8898925/is-there-a-reason-for-cs-reuse-of-the-variable-in-a-foreach – 2013-02-16 07:22:15

+1

TL; DR答案:添加.ToList()或.ToArray()在你的查詢表達式的末尾,它將擺脫警告 – JoelFan 2015-08-25 15:18:50

回答

134

此警告有兩個部分。首先是...在封閉

...這是不是無效本身

訪問的foreach變量,但它是反直覺的第一眼。做對也很困難。 (以至於我在下面鏈接的文章將其描述爲「有害」)。

請注意,您摘錄的代碼基本上是C#編譯器(在C#5之前)的擴展形式,生成foreach

我[不]明白爲什麼[以下是]無效:

string s; while (enumerator.MoveNext()) { s = enumerator.Current; ... 

嗯,這是有效的語法。如果你在循環中所做的只是使用的值s那麼一切都很好。但超過s將導致違反直覺的行爲。看看下面的代碼:

var countingActions = new List<Action>(); 

var numbers = from n in Enumerable.Range(1, 5) 
       select n.ToString(CultureInfo.InvariantCulture); 

using (var enumerator = numbers.GetEnumerator()) 
{ 
    string s; 

    while (enumerator.MoveNext()) 
    { 
     s = enumerator.Current; 

     Console.WriteLine("Creating an action where s == {0}", s); 
     Action action =() => Console.WriteLine("s == {0}", s); 

     countingActions.Add(action); 
    } 
} 

如果你運行這段代碼,你會得到以下控制檯輸出:

Creating an action where s == 1 
Creating an action where s == 2 
Creating an action where s == 3 
Creating an action where s == 4 
Creating an action where s == 5 

這是你所期望的。

要看到的東西你可能沒有想到,運行下面的代碼後立即上面的代碼:

foreach (var action in countingActions) 
    action(); 

您將獲得以下控制檯輸出:

s == 5 
s == 5 
s == 5 
s == 5 
s == 5 

爲什麼?因爲我們創建了五個函數,它們都完全相同:打印s(我們已經關閉)的值。實際上,它們是相同的功能(「打印s」,「打印s」,「打印s」...)。

在我們開始使用它們的時候,它們完全按照我們的要求:打印s的值。如果您查看s的最後一個已知值,您會看到它是5。所以我們得到s == 5五次打印到控制檯。

這正是我們所要求的,但可能不是我們想要的。

警告的第二部分...當使用不同版本的編譯器編譯

可能有不同的行爲。

......就是這樣。 Starting with C# 5, the compiler generates different code that "prevents" this from happening via foreach

因此,下面的代碼將產生在不同版本的編譯器不同的結果:

foreach (var n in numbers) 
{ 
    Action action =() => Console.WriteLine("n == {0}", n); 
    countingActions.Add(action); 
} 

因此,還會產生將R#警告:)

我的第一個代碼段,上方,將在所有版本的編譯器中都表現出相同的行爲,因爲我沒有使用foreach(相反,我已經將它擴展到C#5之前的編譯器的方式)。

這是用於CLR版本嗎?

我不太確定你在問什麼。

Eric Lippert的文章稱這種變化發生在「C#5」中。所以大概你必須使用C#5或更高版本的編譯器將.NET 4.5或更高版本的目標設爲以獲得新的行爲,並且之前的所有內容都會得到舊的行爲。

但要清楚,它是編譯器的功能,而不是.NET Framework版本。

與IL有關嗎?

不同的代碼會產生不同的IL,因此在這個意義上說IL會產生後果。

foreach是比您在評論中發佈的代碼更常見的結構。這個問題通常是通過使用foreach而不是通過手動枚舉產生的。這就是爲什麼在C#5中對foreach的更改有助於防止此問題,但不完全。

+6

我已經嘗試了使用相同的目標(.Net 3.5)獲得不同結果的不同編譯器的foreach循環。我使用VS2010(反過來使用與.net 4.0相關的編譯器,我相信)和VS2012(我相信.net 4.5編譯器)。原則上,這意味着如果您使用VS2013並編輯一個針對.Net 3.5的項目,並將其構建在已安裝稍舊框架的構建服務器上,則可以看到您的計算機上的程序與部署的構建有不同的結果。 – Ykok 2014-03-13 13:27:57

+0

很好的答案,但不確定「foreach」是如何相關的。這不會發生與手動枚舉,或甚至簡單的(int i = 0;我 Brad 2014-07-14 12:53:29

+0

這裏的'foreach'這個東西來自於這個問題的內容。你說得對,它可以以各種更一般的方式發生。 – 2014-07-14 12:57:59

12

第一個答案很好,所以我想我只是添加一件事。

因爲在您的示例代碼中,reflectModel被分配了一個IEnumerable,而這隻會在枚舉時進行評估,並且枚舉本身可能發生在循環之外,如果您將reflectModel分配給了某些東西範圍更廣。

如果更改

...Where(x => x.Name == property.Value)

...Where(x => x.Name == property.Value).ToList()

然後reflectedModel將被分配foreach循環內的肯定列表,這樣你就不會收到警告,因爲枚舉肯定會發生在循環內,而不是在循環之外。

+0

我讀了很多非常長的解釋,並沒有爲我解決這個問題,然後是一個很短的解決方案。謝謝! – 2015-07-22 20:37:14

+0

我閱讀了接受的答案,只是想「如果它沒有約束變量,它是如何閉包的?」但現在我明白這是關於什麼時候評估發生的,謝謝! – Jerome 2015-08-28 12:13:30

+0

是的,這是顯而易見的通用解決方案。緩慢的,內存密集型的,但我認爲它對所有情況都是100%的工作。 – 2016-10-26 17:32:14

8

塊範圍的變量應該解決警告。

foreach (var entry in entries) 
{ 
    var en = entry; 
    var result = DoSomeAction(o => o.Action(en)); 
} 
+0

簡短和重點。謝謝... – raider33 2017-04-11 13:56:39