我得到以下警告:訪問的foreach變量封警告
訪問的foreach在封閉的可變。使用不同版本的編譯器編譯時可能會有不同的行爲。
這就是它看起來像在我的編輯器:
我知道如何解決這個問題的警告,但我想知道爲什麼我會得到這樣的警告?
這是關於「CLR」版本嗎?它與「IL」有關嗎?
我得到以下警告:訪問的foreach變量封警告
訪問的foreach在封閉的可變。使用不同版本的編譯器編譯時可能會有不同的行爲。
這就是它看起來像在我的編輯器:
我知道如何解決這個問題的警告,但我想知道爲什麼我會得到這樣的警告?
這是關於「CLR」版本嗎?它與「IL」有關嗎?
此警告有兩個部分。首先是...在封閉
...這是不是無效本身
訪問的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
的更改有助於防止此問題,但不完全。
我已經嘗試了使用相同的目標(.Net 3.5)獲得不同結果的不同編譯器的foreach循環。我使用VS2010(反過來使用與.net 4.0相關的編譯器,我相信)和VS2012(我相信.net 4.5編譯器)。原則上,這意味着如果您使用VS2013並編輯一個針對.Net 3.5的項目,並將其構建在已安裝稍舊框架的構建服務器上,則可以看到您的計算機上的程序與部署的構建有不同的結果。 – Ykok 2014-03-13 13:27:57
很好的答案,但不確定「foreach」是如何相關的。這不會發生與手動枚舉,或甚至簡單的(int i = 0;我
這裏的'foreach'這個東西來自於這個問題的內容。你說得對,它可以以各種更一般的方式發生。 – 2014-07-14 12:57:59
第一個答案很好,所以我想我只是添加一件事。
因爲在您的示例代碼中,reflectModel被分配了一個IEnumerable,而這隻會在枚舉時進行評估,並且枚舉本身可能發生在循環之外,如果您將reflectModel分配給了某些東西範圍更廣。
如果更改
...Where(x => x.Name == property.Value)
到
...Where(x => x.Name == property.Value).ToList()
然後reflectedModel將被分配foreach循環內的肯定列表,這樣你就不會收到警告,因爲枚舉肯定會發生在循環內,而不是在循環之外。
我讀了很多非常長的解釋,並沒有爲我解決這個問題,然後是一個很短的解決方案。謝謝! – 2015-07-22 20:37:14
我閱讀了接受的答案,只是想「如果它沒有約束變量,它是如何閉包的?」但現在我明白這是關於什麼時候評估發生的,謝謝! – Jerome 2015-08-28 12:13:30
是的,這是顯而易見的通用解決方案。緩慢的,內存密集型的,但我認爲它對所有情況都是100%的工作。 – 2016-10-26 17:32:14
塊範圍的變量應該解決警告。
foreach (var entry in entries)
{
var en = entry;
var result = DoSomeAction(o => o.Action(en));
}
簡短和重點。謝謝... – raider33 2017-04-11 13:56:39
http://stackoverflow.com/questions/8898925/is-there-a-reason-for-cs-reuse-of-the-variable-in-a-foreach – 2013-02-16 07:22:15
TL; DR答案:添加.ToList()或.ToArray()在你的查詢表達式的末尾,它將擺脫警告 – JoelFan 2015-08-25 15:18:50