為什麼在 VS 2010 和 VS 2012 中建構的應用程序的行為存在差異?
我正在檢查在我們的建構機器上安裝 .NET 4.5 是否會更改 VS 2010 生成的輸出 IL 映像。
由於我知道 foreach 的行為在 .NET 4.5 中發生了變化,以避免由於Access to Modified closure導致的問題,因此我選擇了一個展示該行為的簡單應用程序。
class Program { private static void Main(string[] args) { var contents = new List<Func<int>(); var s = new StringBuilder(); int[] values = new int[] { 4, 5, 6 }; foreach (int value in values) { contents.Add(() => value); } for (var k = 0; k < contents.Count; k++) s.Append(contents[k]()); Console.WriteLine(s); }VS 2010 輸出:666
VS 2012 輸出:456
我在 VS 2010 中創建了一個控制台應用程序,並在 VS 2012 中創建了一個具有相同程式碼的控制台應用程序(均針對 .NET 4)。
但是,這兩個控制台應用程序根據建構它們的 IDE 表現出不同的行為。在建構輸出中,我檢查了兩者是否具有幾乎相似的建構參數。所以我想知道最終執行檔如何表現出不同的行為?.NET 4.5 是就地升級,因此兩個 IDE 的編譯器必須相同。
注意:我確實看過一個相關問題:VS 2010 和 VS 2012 中的不同 LINQ 答案,但它沒有回答我關於為什麼可執行行為不同的問題。
編輯 1: 正如mletterle提到的,我確實嘗試在 VS 2010 命令提示符下使用 VS 2010 輸出視窗中的命令行建構程式碼。生成的輸出就像是用 VS 2012 建構的一樣。
編輯2:
我在輸出視窗中發布輸出:
VS 2010: 建構開始於 2012 年 12 月 20 日晚上 11:04:56。
CoreClean:創建目錄“obj\x86\Debug\”。GenerateTargetFrameworkMonikerAttribute:跳過目標“GenerateTargetFrameworkMonikerAttribute”,因為所有輸出文件相對於輸入文件都是最新的。核心編譯:
C:\Windows\Microsoft.NET\Framework\v4.0.30319\Csc.exe /noconfig /nowarn:1701,1702 /nostdlib+ /platform:x86 /errorreport:prompt /warn:4 /define:DEBUG;TRACE /errorendlocation /preferreduilang :en-US /highentropyva- /reference:“C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework.NETFramework\v4.0\Microsoft.CSharp.dll” /reference:“C:\Program Files (x86 )\Reference Assemblies\Microsoft\Framework.NETFramework\v4.0\mscorlib.dll” /reference:“C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework.NETFramework\v4.0\System.Core.dll " /reference:“C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework.NETFramework\v4.0\System.Data.DataSetExtensions.dll” /reference:“C:\Program Files (x86)\Reference Assemblies \Microsoft\Framework.NETFramework\v4.0\System.Data。dll” /reference:“C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework.NETFramework\v4.0\System.dll” /reference:“C:\Program Files (x86)\Reference Assemblies\Microsoft\ Framework.NETFramework\v4.0\System.Xml.dll” /reference:“C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework.NETFramework\v4.0\System.Xml.Linq.dll” /debug+ /debug:full /filealign:512 /optimize- /out:obj\x86\Debug\TestConsoleApp.exe /target:exe /utf8output Program.cs Properties\AssemblyInfo.cs “C:\Users\105044960\AppData\Local\Temp .NETFramework,Version=v4.0.AssemblyAttributes.cs” _CopyAppConfigFile: 跳過目標 “_CopyAppConfigFile” 因為所有輸出文件相對於輸入文件都是最新的。CopyFilesToOutputDirectory: 複製文件從 “obj\x86\Debug\TestConsoleApp.exe” 到 “bin\Debug\TestConsoleApp.exe”。TestConsoleApp -> C:\Users\105044960\Documents\Visual Studio 2010\Projects\TestConsoleApp\TestConsoleApp\bin\Debug\TestConsoleApp.exe將文件從“obj\x86\Debug\TestConsoleApp.pdb”複製到“bin\Debug\TestConsoleApp.pdb”。
與 2012 年相比:
1>CoreClean: 1> 刪除文件“c:\users\105044960\documents\visual studio 11\Projects\TestConsoleApp\TestConsoleApp\bin\Debug\TestConsoleApp.exe”。1> 刪除文件“c:\users\105044960\documents\visual studio 11\Projects\TestConsoleApp\TestConsoleApp\bin\Debug\TestConsoleApp.pdb”。1> 刪除文件“c:\users\105044960\documents\visual studio 11\Projects\TestConsoleApp\TestConsoleApp\obj\Debug\TestConsoleApp.csprojResolveAssemblyReference.cache”。1> 刪除文件“c:\users\105044960\documents\visual studio 11\Projects\TestConsoleApp\TestConsoleApp\obj\Debug\TestConsoleApp.exe”。1> 刪除文件“c:\users\105044960\documents\visual studio 11\Projects\TestConsoleApp\TestConsoleApp\obj\Debug\TestConsoleApp.pdb”。1>GenerateTargetFrameworkMonikerAttribute:1>跳過目標“GenerateTargetFrameworkMonikerAttribute”,因為所有輸出文件相對於輸入文件都是最新的。1>CoreCompile: 1> C:\Windows\Microsoft.NET\Framework\v4.0.30319\Csc.exe /noconfig /nowarn:1701,1702,2008 /nostdlib+ /platform:AnyCPU /errorreport:prompt /warn:4 /define :DEBUG;TRACE /errorendlocation /preferreduilang:en-US /highentropyva- /reference:“C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework.NETFramework\v4.0\Microsoft.CSharp.dll” /reference: “C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework.NETFramework\v4.0\mscorlib.dll” /reference:“C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework.NETFramework\v4 .0\System.Core.dll” /reference:“C:
Visual Studio 使用程序內編譯器,因此它知道它使用的是哪個版本的 C#。
正如您所指出的,另一方面,命令行中的 csc.exe 使用它要編譯的任何 C# 版本,因此在您的情況下它將是 C# 5.0。由於它是就地升級(就安裝目錄而言),它可能會破壞依賴於
foreach綁定在整個循環中相同的程式碼(奇怪,但可能)。注意:錯誤問題的舊答案:OP 知道這一點並正在從命令行對其進行測試。
您連結到的部落格文章已經回答了您的問題。我認為這個問題與這個有關。
改變的是編譯器,所以:
foreach (int value in values) { // ... }用於根據以下程式碼生成一些東西:
{ int value; for (/* iteration */) { value = /* get from enumerator */; // ... } }而新的 C# 編譯器現在生成相當於將變數移動到循環內部:
for (/* iteration */) { int value = /* get from enumerator */; // ... }這有很大的不同,因為在每個循環中的閉包
// ...將擷取一個新的value綁定,而不是共享value過去在循環外聲明的相同綁定。
foreach問題是,如果您希望程式碼在較舊和較新的編譯器上都能正常工作,則必須在循環內聲明自己的變數:foreach (int value in values) { int newValue = value; // ... }Visual Studio 2010 中目前的 C# 4.0 規範說:
(…) 形式的 foreach 語句
foreach (V v in x) embedded-statement然後擴展為:
{ E e = ((C)(x)).GetEnumerator(); try { V v; while (e.MoveNext()) { v = (V)(T)e.Current; embedded-statement } } finally { … // Dispose e } }Visual Studio 2012 中的 C# 5.0 規範說:
(…) 形式的 foreach 語句
foreach (V v in x) embedded-statement然後擴展為:
{ E e = ((C)(x)).GetEnumerator(); try { while (e.MoveNext()) { V v = (V)(T)e.Current; embedded-statement } } finally { … // Dispose e } }
注意:我刪除了大部分原始回复。它回答了錯誤的問題。接下來是更好的回應。
啊,現在我明白你在問什麼:“Visual Studio 2010 如何知道在安裝 .NET 4.5 後編譯為 C# 4 而不是 C# 5,即使 Visual Studio 2010 和 Visual Studio 2012 使用相同的 csc.exe 並通過有同樣的選擇嗎?”
@mletterle 但是 .NET 4.5 是對 .NET 4 的就地升級。所以實際上我的機器上只有 .NET 4。唯一的可能是 IDE 隱藏了我看不到的 .NET 4 編譯器的隱藏副本。
我不確定你是從哪裡聽到的,或者你為什麼這麼認為。.NET 4.5 不是就地升級。它是該工具的不同版本。會有差異。這是其中之一。
更新1:
看起來我們使用了不同的“就地”升級定義。我對“就地”的使用是“版本之間應該沒有明顯差異的升級”。您連結到的文章中給出的定義以不同的方式使用它:“就地”在它們的用法中是“使用相同的 CLR,但添加了新的庫”。
由於 C# 5 與 C# 4 不同,因此在我熟悉的用法中,這種更改並不是“到位”的。
結果,差異不是您所針對的 CLR,而是您正在使用的語言版本 - CLR 是“就地”升級(都是 4.0 CLR),但語言不是(VS2010 中的 C# 4 , VS2012 中的 C#5。)
更新 2:
在 .csproj 文件(實際上是由 Visual Studio 管理的 msbuild 文件)中,有一個指定目標框架的屬性。預設情況下,使用 Visual Studio 2012 製作的項目具有以下功能:
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>而針對版本 4 的 Visual Studio 2010 中的項目如下所示:
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>這告訴 Visual Studio 在為一個或另一個目標框架建構時設置環境。雖然看起來 csc.exe 是直接從命令提示符呼叫的,但實際上並非如此:msbuild 項目實際上是正在處理的內容,並且它發生在“Visual Studio”的自定義程序環境中。
我只能假設正在發生的事情的細節,但很可能在升級之後,將“TargetFrameworkVersion”屬性設置為 v4.0 在編譯針對 v4.0 的項目期間會將環境返回到 v4.0。另一方面,通過在沒有 msbuild 設置的環境的情況下從命令行呼叫 csc.exe,它使用其版本的“預設值”(現在預設為 C# 5)為您提供新的 C#5 行為,即使您’正在使用 VS 2010 命令提示符。但是,當您通過 MSBuild 呼叫建構時,它知道如何在建構期間返回原始 C# 4 環境(因為 MSBuild 也是 .NET 工具鏈的一部分。)