为什么使用VS 2010和VS 2012构建的应用程序行为不同?

19

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

既然我知道foreach的行为在.NET 4.5中已经改变,以避免因访问修改闭包而出现问题,我选择了一个展示这种行为的简单应用程序。

  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 2012构建的一样。

编辑2:

我正在发布输出窗口中的输出:

VS 2010: Build started on December 20, 2012 at 11:04:56 PM.

CoreClean: Creating directory "obj\x86\Debug\". GenerateTargetFrameworkMonikerAttribute: Skipping target "GenerateTargetFrameworkMonikerAttribute" because all output files are up-to-date with respect to the input files. 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)\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: Skipping target "_CopyAppConfigFile" because all output files are up-to-date with respect to the input files. CopyFilesToOutputDirectory: Copying file from "obj\x86\Debug\TestConsoleApp.exe" to "bin\Debug\TestConsoleApp.exe". TestConsoleApp -> C:\Users\105044960\Documents\Visual Studio 2010\Projects\TestConsoleApp\TestConsoleApp\bin\Debug\TestConsoleApp.exe Copying file from "obj\x86\Debug\TestConsoleApp.pdb" to "bin\Debug\TestConsoleApp.pdb".

VS 2012:

CoreClean: 删除文件 "c:\users\105044960\documents\visual studio 11\Projects\TestConsoleApp\TestConsoleApp\bin\Debug\TestConsoleApp.exe"、"c:\users\105044960\documents\visual studio 11\Projects\TestConsoleApp\TestConsoleApp\bin\Debug\TestConsoleApp.pdb"、"c:\users\105044960\documents\visual studio 11\Projects\TestConsoleApp\TestConsoleApp\obj\Debug\TestConsoleApp.csprojResolveAssemblyReference.cache"、"c:\users\105044960\documents\visual studio 11\Projects\TestConsoleApp\TestConsoleApp\obj\Debug\TestConsoleApp.exe" 和 "c:\users\105044960\documents\visual studio 11\Projects\TestConsoleApp\TestConsoleApp\obj\Debug\TestConsoleApp.pdb"。GenerateTargetFrameworkMonikerAttribute: 跳过目标 "GenerateTargetFrameworkMonikerAttribute",因为所有输出文件都是最新的。CoreCompile: 编译程序并生成可执行文件 TestConsoleApp.exe 和调试信息 TestConsoleApp.pdb,输出到目录 bin\Debug 下。

它们各自表现出的不同行为是什么? - Andrew Barber
2
那么,你为什么不认为这个链接解释了它呢? - jalf
1
@GaneshR. 但是,难道这不是在你提供的答案中解释了吗(而且又链接到了this)? 这个改变是因为编译器在实现foreach循环时发生了变化。也许我漏掉了什么,但看起来你已经有了问题的答案。 - jalf
@mletterle 但是参考程序集指向的是同一个位置,即C:\ Program Files(x86)\ Reference Assemblies \ Microsoft \ Framework \ .NETFramework \ v4.0 \。 - Ganesh R.
1
@jalf - 这个变化与 .NET Framework 本身无关,而是与 C# 5.0 相关。 - Security Hound
显示剩余20条评论
2个回答

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),但语言不是(C# 4在VS2010中,C# 5在VS2012中)。
更新2:
在.csproj文件中(实际上是由Visual Studio管理的msbuild文件),有一个指定目标框架的属性。使用Visual Studio 2012创建的项目默认具有此功能:
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>

如果在 Visual Studio 2010 中,目标版本为 4 的项目看起来像:

<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>

这段话告诉Visual Studio在为一个或另一个目标框架构建时设置环境。虽然看起来像是直接从命令提示符中调用csc.exe,但实际上不是这样的:msbuild项目才是正在处理的内容,并且它是在“Visual Studio”的自定义进程环境中进行的。
我只能假设具体发生了什么,但很可能在升级后,将“TargetFrameworkVersion”属性设置为v4.0会在编译针对v4.0的项目时返回到v4.0的环境。另一方面,通过在没有由msbuild设置的环境中从命令行调用csc.exe,它使用其版本的“默认值”(现在默认为C# 5),即使您使用VS 2010命令提示符也会得到新的C# 5行为。但是,当您通过MSBuild调用构建时,它知道如何在构建期间返回原始的C# 4环境(因为MSBuild也是.NET工具链的一部分)。

2
.NET 4.5 是 .NET 4 的就地升级版本。请参考 http://www.hanselman.com/blog/NETVersioningAndMultiTargetingNET45IsAnInplaceUpgradeToNET40.aspx 。我也了解编译器行为的变化方式。我的问题是,当底层框架组件相同时,为什么在使用不同版本的 IDE 时会有所不同? - Ganesh R.
1
是的,这就解释了为什么当csc.exe编译C#5代码时它会做什么。它没有解释csc.exe如何知道在VS2010中被调用时它正在编译C#4代码,这是OP问题的根源。 - mletterle
2
@Ramhound 这并没有解释当将文本文件交给csc.exe时,它如何知道正在使用哪种语法,特别是当内容与OP中的内容没有区别时。 - mletterle
2
@Ramhound 相同的 csc.exe 可執行檔,在相同的磁碟位置上,被 VS2010 和 VS2012 用於編譯不同的 C# 版本,沒有明顯的命令引數導致這種情況。最根本的分歧在於人們認為 C# 編譯器與 .NET Framework 是分開的,但它實際上並不是。CSC 編譯器與 .NET Framework 一起安裝在 %SYSTEMDIR%\Microsoft.NET\Framework\[Version]。在 .NET 4.0 上安裝 .NET 4.5 將其替換。 - mletterle
-1 是指 <TargetFrameworkVersion/> 决定了将使用哪个编译器。只要您使用 4.5 编译器(在使用 msbuild 时不可避免),即使针对 4.0,您仍然可以使用 4.5 编译器功能,例如 CallerFileNameAttribute,借助于 Microsoft.Bcl nuget 包。 - binki
显示剩余6条评论

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

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

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

是编译器发生了变化,所以这个:

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规范如下:

(...) A foreach statement of the form

foreach (V v in x) embedded-statement

is then expanded to:

{
  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 规范如下所述:

(...) A foreach statement of the form

foreach (V v in x) embedded-statement

is then expanded to:

{
  E e = ((C)(x)).GetEnumerator();
  try {
      while (e.MoveNext()) {
          V v = (V)(T)e.Current;
          embedded-statement
      }
  }
  finally {
      … // Dispose e
  }
}

@RobertP,确实,现在我看到了真正的问题,也看到你编辑了你的答案。我也会编辑我的回答。 - acelent
@PauloMadeira 看起来他们在 VS 2005/2008 中所做的仍然是正确的。顺便感谢你的回答。 - Ganesh R.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接