为什么在VS 2010和VS 2012中构建的应用程序的行为存在差异?

Gan*_* R. 19 .net visual-studio-2010 visual-studio-2012

我正在检查在我们的构建机器上安装.NET 4.5是否会更改VS 2010生成的输出IL映像.

因为我知道在.NET 4.5中foreach的行为已经改变以避免由于访问修改闭包引起的问题,所以我选择了一个展示该行为的简单应用程序.

  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);
        }
Run Code Online (Sandbox Code Playgroud)

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: Build 12/20/2012 11:04:56 PM

CoreClean:创建目录"obj\x86\Debug \".GenerateTargetFrameworkMonikerAttribute:跳过目标"GenerateTargetFrameworkMonikerAttribute",因为所有输出文件都是相对于输入文件的最新文件.CoreCompile:
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)\参考程序集\ Microsoft\Framework.NETFramework\v4.0\System.Data.dll"/ reference:"C:\ Program Files(x86)\ Reference Assemblies\Microsoft\Framework.NETFramework\v4.0\System.dll"/参考:"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".

VS 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:\ Program Files(x86)\ Reference Assemblies\Microsoft\Framework.NETFramework\v4.0\System.Data.DataSetExtensions.dll"/ reference:"C:\程序文件(x86)\参考程序集\ 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.NETFr amework\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\Debug\TestConsoleApp.exe/target:exe/utf8output Program.cs Properties\AssemblyInfo.cs"C:\ Users\105044960\AppData\Local\Temp.NETFramework,Version = v4.0.AssemblyAttributes.cs"1> CopyFilesToOutputDirectory:1>将文件从"obj\Debug\TestConsoleApp.exe"复制到"bin\Debug\TestConsoleApp.exe".1> TestConsoleApp - > C:\ Users\105044960\Documents\Visual Studio 11\Projects\TestConsoleApp\TestConsoleApp\bin\Debug\TestConsoleApp.exe 1>将文件从"obj\Debug\TestConsoleApp.pdb"复制到"bin\Debug"\TestConsoleApp.pdb".

Rob*_*t P 8

注意:我删除了原始回复的大部分内容.它回答了错误的问题.随后会有更好的回应.

啊,现在我看到你在问什么:"安装.NET 4.5后,Visual Studio 2010如何知道编译到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>
Run Code Online (Sandbox Code Playgroud)

Visual Studio 2010中针对版本4的项目如下所示:

<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
Run Code Online (Sandbox Code Playgroud)

这告诉Visual Studio在构建一个或另一个目标框架时设置环境.虽然看起来直接从命令提示符调用csc.exe,但实际上并非如此:msbuild项目实际上是正在处理的内容,它发生在"Visual Studio"的自定义进程环境中.

我只能假设发生了什么的具体细节,但是在升级之后,将"TargetFrameworkVersion"属性设置为v4.0会在编译面向v4.0的项目期间将环境返回到v4.0.另一方面,通过从命令行调用csc.exe而没有msbuild设置的环境,它使用其版本的"默认值"(现在默认为C#5),即使你使用了新的C#5行为'使用VS 2010命令提示符.但是,当您通过MSBuild调用构建时,它知道如何在构建期间返回到原始C#4环境(因为MSBuild也是.NET工具链的一部分).

  • .NET 4.5是对.NET 4的适当升级.请参阅http://www.hanselman.com/blog/NETVersioningAndMultiTargetingNET45IsAnInplaceUpgradeToNET40.aspx此外,我了解编译器行为的更改方式.我的问题是,当底层Framework组件相同时,当我使用不同版本的IDE时,为什么它会有所不同 (2认同)
  • @Ramhound这并没有解释在将文本文件传递给csc.exe时它如何知道正在使用哪种语法,特别是当OP中的内容没有差异时. (2认同)
  • @Ramhound VS2010和VS2012使用位于磁盘上相同位置的相同csc.exe可执行文件来编译不同的C#版本,没有明显的命令参数导致这种情况.根本的断开是人们认为CSharp编译器与.NET Framework是分开的,它不是.CSC编译器与.NET Framework一起安装在%SYSTEMDIR%\ Microsoft.NET\Framework \\ [Version]中.在.NET 4.0之上安装.NET 4.5取代了它. (2认同)

ace*_*ent 3

Visual Studio 使用进程内编译器,因此它知道它正在使用哪个版本的 C#。

\n\n

正如您所指出的,另一方面,命令行中的 csc.exe 使用它编译的任何 C# 版本,因此在您的情况下它将是 C# 5.0。由于它是就地升级(就安装目录而言),因此它可能会破坏依赖于foreach整个循环中相同的绑定的代码(奇怪,但可能)。

\n\n
\n\n
\n

注意:错误问题的旧答案:OP知道这一点并且正在从命令行测试它。

\n
\n\n

您链接到的博客文章已经回答了您的问题。我认为这个问题与这个有关。

\n\n

是编译器改变了,所以:

\n\n
foreach (int value in values)\n{\n    // ...\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

用于沿着以下代码生成一些内容:

\n\n
{\n    int value;\n    for (/* iteration */)\n    {\n        value = /* get from enumerator */;\n        // ...\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

而新的 C# 编译器现在生成将变量移动到循环内部的等效项:

\n\n
for (/* iteration */)\n{\n    int value = /* get from enumerator */;\n    // ...\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

这有很大的不同,因为 中的闭包// ...将在每个循环中捕获一个新的value绑定,而不是共享value过去在循环外声明的相同绑定。

\n\n

问题是,如果您希望代码在旧版和新版编译器中都能正常工作,则必须在循环内声明自己的变量foreach

\n\n
foreach (int value in values)\n{\n    int newValue = value;\n    // ...\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n
\n\n

Visual Studio 2010 中当前的 C# 4.0 规范规定:

\n\n
\n

(...) 形式的 foreach 语句

\n\n
foreach (V v in x) embedded-statement\n
Run Code Online (Sandbox Code Playgroud)\n\n

然后扩展为:

\n\n
{\n  E e = ((C)(x)).GetEnumerator();\n  try {\n      V v;\n      while (e.MoveNext()) {\n          v = (V)(T)e.Current;\n          embedded-statement\n      }\n  }\n  finally {\n      \xe2\x80\xa6 // Dispose e\n  }\n}\n
Run Code Online (Sandbox Code Playgroud)\n
\n\n

Visual Studio 2012 中的 C# 5.0 规范表示:

\n\n
\n

(...) 形式的 foreach 语句

\n\n
foreach (V v in x) embedded-statement\n
Run Code Online (Sandbox Code Playgroud)\n\n

然后扩展为:

\n\n
{\n  E e = ((C)(x)).GetEnumerator();\n  try {\n      while (e.MoveNext()) {\n          V v = (V)(T)e.Current;\n          embedded-statement\n      }\n  }\n  finally {\n      \xe2\x80\xa6 // Dispose e\n  }\n}\n
Run Code Online (Sandbox Code Playgroud)\n
\n