19

ビルド マシンに .NET 4.5 をインストールすると、VS 2010 によって生成される出力 IL イメージが変更されるかどうかを確認していました。

Access to Modified クロージャによる問題を回避するために、.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);
        }

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」にファイルをコピーしています。

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:

4

2 に答える 2

8

注: 元の応答の多くを削除しました。間違った質問に答えていました。より良い応答が続きます。

ああ、今私はあなたが尋ねていることを見ます:「.NET 4.5 がインストールされた後、Visual Studio 2010 は C# 5 ではなく C# 4 にコンパイルすることをどのように認識しますか? Visual Studio 2010 と Visual Studio 2012 でさえ同じ csc.exe を使用してパスそれに同じオプション?」

@mletterleしかし、.NET 4.5は.NET 4へのインプレースアップグレードです。したがって、実際には私のマシンには.NET 4しか存在しません。唯一の可能性は、私には見えない .NET 4 コンパイラの隠しコピーが IDE に隠されていることです。

どこでそれを聞いたのか、なぜそう思ったのかわかりません。.NET 4.5 はインプレース アップグレードではありません。これはツールの別のバージョンです。違いがあります。これはそれらの1つです。

更新 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 */;
    // ...
}

ループの外側で宣言されていた同じバインディングを共有するのではなく、 内のクロージャーが各サイクルで// ...新しいバインディングをキャプチャするため、これは大きな違いを生みます。valuevalue

問題は、古いコンパイラと新しいコンパイラの両方でコードを正しく動作させたい場合は、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 に答える