19

我正在检查在我们的构建机器上安装 .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:

4

2 回答 2

8

注意:我删除了大部分原始回复。它回答了错误的问题。接下来是更好的回应。

啊,现在我明白你在问什么:“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 工具链的一部分。)

于 2012-12-20T18:09:16.880 回答
3

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
  }
}
于 2012-12-20T18:43:04.383 回答