19

I was checking whether installing .NET 4.5 on our build machines changes the output IL image generated by VS 2010.

Since I know the behaviour of foreach has changed in .NET 4.5 to avoid issues due to Access to Modified closure, I chose a simple application that exhibited the behaviour.

  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 output: 666

VS 2012 output: 456

I created a console application in VS 2010 and a console application with the same code in VS 2012 (both targeted .NET 4).

However, both the console applications exhibited different behaviours based on the IDE they were built with. In the build output, I checked that both had nearly similar build arguments. So I was wondering how the end executable exhibited different behaviour? .NET 4.5 is an in-place upgrade, so the compiler for both IDEs must be the same.

NOTE: I did have a look at a related question: Different LINQ Answer in VS 2010 and VS 2012 but it did not answer my question on why the executable behaviour differed.

EDIT 1: As mletterle mentioned, I did try building the code using the commandline in the output window of VS 2010 in a VS 2010 command prompt. The resulting output behaved as if it was built with VS 2012.

EDIT 2:

I am posting the output present in Output Window:

VS 2010: Build started 12/20/2012 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:

1>CoreClean: 1> Deleting file "c:\users\105044960\documents\visual studio 11\Projects\TestConsoleApp\TestConsoleApp\bin\Debug\TestConsoleApp.exe". 1> Deleting file "c:\users\105044960\documents\visual studio 11\Projects\TestConsoleApp\TestConsoleApp\bin\Debug\TestConsoleApp.pdb". 1> Deleting file "c:\users\105044960\documents\visual studio 11\Projects\TestConsoleApp\TestConsoleApp\obj\Debug\TestConsoleApp.csprojResolveAssemblyReference.cache". 1> Deleting file "c:\users\105044960\documents\visual studio 11\Projects\TestConsoleApp\TestConsoleApp\obj\Debug\TestConsoleApp.exe". 1> Deleting file "c:\users\105044960\documents\visual studio 11\Projects\TestConsoleApp\TestConsoleApp\obj\Debug\TestConsoleApp.pdb". 1>GenerateTargetFrameworkMonikerAttribute: 1>Skipping target "GenerateTargetFrameworkMonikerAttribute" because all output files are up-to-date with respect to the input files. 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:\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\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> Copying file from "obj\Debug\TestConsoleApp.exe" to "bin\Debug\TestConsoleApp.exe". 1> TestConsoleApp -> C:\Users\105044960\Documents\Visual Studio 11\Projects\TestConsoleApp\TestConsoleApp\bin\Debug\TestConsoleApp.exe 1> Copying file from "obj\Debug\TestConsoleApp.pdb" to "bin\Debug\TestConsoleApp.pdb".

Community
  • 1
  • 1
Ganesh R.
  • 4,337
  • 3
  • 30
  • 46
  • What is the different behavior each exhibits? – Andrew Barber Dec 20 '12 at 17:07
  • Er, why exactly are you surprised? You linked to a blog post explaining the different behavior... – jalf Dec 20 '12 at 17:20
  • @jalf The same code targeting the same .NET version on the same machine is giving different outputs depending on the IDE version is use. – Ganesh R. Dec 20 '12 at 17:23
  • @AndrewBarber Please see comment above – Ganesh R. Dec 20 '12 at 17:24
  • 2
    So why don't you think [this](http://stackoverflow.com/questions/13327224/different-linq-answer-in-vs-2010-and-vs-2012) explains it? – jalf Dec 20 '12 at 17:24
  • Want to see something even more interesting? Try building the VS2010 solution from the VS2010 command prompt using MSBuild... – mletterle Dec 20 '12 at 17:26
  • @jalf Both are using the same compiler to compile the code. So why are they giving different outputs? I just wanted to know where are the two IDEs differing thats it – Ganesh R. Dec 20 '12 at 17:26
  • @mletterle Yes. I tried that. The application that is created starts behaving like the one built with VS 2012 IDE – Ganesh R. Dec 20 '12 at 17:27
  • 1
    @GaneshR. but again, isn't that explained in the answer you linked to (and which again links to [this](http://msmvps.com/blogs/kathleen/archive/2012/07/03/lifting-foreach-breaking-change-in-visual-studio-2012.aspx)? It is changed because there has been a change in how the compiler implements the foreach loop. Maybe I'm missing something, but looks like you already have the answer to your question – jalf Dec 20 '12 at 17:29
  • If I had to guess I would say the CSC compiler is smart enough to switch between C#4 and C#5 based on what .NET reference assemblies are used. – mletterle Dec 20 '12 at 17:29
  • @jailf It isn't as straight forward as your assuming, the behavior is unintuitive because the same compiler executable is used for both VS2010 and VS2012 projects. So one would expect to see the new behavior from VS2010 as well, but you don't. – mletterle Dec 20 '12 at 17:30
  • @mletterle But the reference assemblies are pointing to the same location i.e. C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0\ – Ganesh R. Dec 20 '12 at 17:35
  • @GaneshR. - Have you switch version of C# that is being used though? C# 5.0 and C# 4.0 share the same assemblies since .NET 4.5 is an inplace replacement for .NET 4.0. – Security Hound Dec 20 '12 at 17:37
  • @Ramhound I am building both the applcations on the same machine. So yes both share the same .NET assemblies and compiler. Only the IDE is different. Also if I build using the commandline that is outputed in VS 2010, the output behaves as if it was built in VS 2012. It is just when I build the application using the VS 2010 IDE, is the behaviuor different – Ganesh R. Dec 20 '12 at 17:39
  • @GaneshR. - Verify both are using the version of C# you want to use. The article explains why this happen. **This is not a bug.** The reason is crystal clear. – Security Hound Dec 20 '12 at 17:41
  • @GaneshR. Probably then, when running in the VS2010 IDE, the 4.0 runtime is the highest loaded runtime, which the CSC compiler then uses to determine what to do, outside of the IDE it finds whatever the highest installed runtime is. – mletterle Dec 20 '12 at 17:44
  • @mletterle But .NET 4.5 is an in place upgrade to .NET 4. So really there is only .NET 4 present on my machine. The only possibility is that the IDE has stashed away a hidden copy of .NET 4 compiler that I cannot see. – Ganesh R. Dec 20 '12 at 17:48
  • @GaneshR. - We all know that. You need to verify which version of C# VS2012 is set to use. I am going to guess its actually using C# 5.0 syntax against the .NET 4.0 Framework. – Security Hound Dec 20 '12 at 17:52
  • @Ramhound How do I check that? I did check that both target .NET 4 – Ganesh R. Dec 20 '12 at 17:54
  • @mletterle but the post he linked to explained that the change in behavior is tied to the IDE version. I haven't looked into exactly how and where the change is implemented, but that post is quite clear about this being a breaking change in VS2012, not in .NET. :) – jalf Dec 20 '12 at 18:12
  • @jalf IMHO, compiling is done by the C# compiler which is part of .NET Framework and not VS IDE. You can compile .NET files without having VS IDE installed. VS IDE just helps us organize the files nicely and give us a one stop shop to build the assemblies instead of having to write a batch file and add all the dependencies for builds to it – Ganesh R. Dec 20 '12 at 18:23
  • @GaneshR. - Look up how you get target a specific version of C#. I don't have access to VS to walk you through it. The blog post by Eric Lippert points out C# 5.0 is the reason for the different output. – Security Hound Dec 20 '12 at 18:29
  • 1
    @jalf - The change is connect to C# 5.0 not the .NET Framework itself. – Security Hound Dec 20 '12 at 18:30
  • @Ramhound I will try to search for that post. Thanks for the u[date – Ganesh R. Dec 20 '12 at 18:36
  • @jalf csc.exe is distributed as part of the Framework, VS2012 installation installs .NET 4.5 which is installed in the same place that .NET 4.0 was installed, overwriting the original csc.exe. – mletterle Dec 20 '12 at 18:41

2 Answers2

8

Note: I removed much of my original response. It was answering the wrong question. A better response follows.

Ah, now I see what you're asking: "How does Visual Studio 2010 know to compile to C# 4 instead of C# 5 after .NET 4.5 is installed, even Visual Studio 2010 and Visual Studio 2012 use the same csc.exe and pass the same options to it?"

@mletterle But .NET 4.5 is an in place upgrade to .NET 4. So really there is only .NET 4 present on my machine. The only possibility is that the IDE has stashed away a hidden copy of .NET 4 compiler that I cannot see.

I'm not sure where you heard that or why you assumed that. .NET 4.5 is NOT an in-place upgrade. It is a different version of the tool. There will be differences. This is one of them.

Update 1:

Looks like we were using a different definition of "in-place" upgrade. My usage of "in-place" is "an upgrade that should have no discernible differences between versions." The definition given in the article you linked to uses it in a different way: "in place" in their usage is "uses the same CLR, but adds new libraries."

Since C# 5 is different than C# 4, that change is NOT "in place" in the usage I'm familiar with.

As a result, the difference is not the CLR you're targeting, but the language version you're using - the CLR is an "in place" upgrade (both the 4.0 CLR), but the language is not (C# 4 in VS2010, C#5 in VS2012.)

Update 2:

Within the .csproj file (which is actually an msbuild file managed by Visual Studio), there is an attribute that specifies the target framework. Projects made with Visual Studio 2012 have this by default:

<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>

Whereas projects in Visual Studio 2010 that target Version 4 look like:

<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>

This tells Visual Studio to set up the environment when building for one or the other target framework. While it looks like csc.exe is being invoked directly from a command prompt, it really isn't: the msbuild project is actually what's being processed, and it's happening in "Visual Studio"'s custom process environment.

I can only assume the specifics of what's happening, but likely after the upgrade, having the "TargetFrameworkVersion" attribute set to v4.0 returns the environment to v4.0 during the compilation of a project targeting v4.0. On the other hand, by invoking csc.exe from the command line without the environment set up by msbuild, it uses the "defaults" for its version (which is now defaulting to C# 5) giving you the new C# 5 behavior even though you're using the VS 2010 command prompt. When you invoke the build via MSBuild, though, it knows how to return to the original C# 4 environment for the duration of the build (since MSBuild is part of the .NET tool chain, too.)

Robert P
  • 15,707
  • 10
  • 68
  • 112
  • 2
    .NET 4.5 is an in place upgrade to .NET 4. Please refer to http://www.hanselman.com/blog/NETVersioningAndMultiTargetingNET45IsAnInplaceUpgradeToNET40.aspx Also I understand in what way the compiler behaviour has changed. My question is why is it differing when I use different versions of IDE when the underlying Framework components are the same – Ganesh R. Dec 20 '12 at 18:20
  • @GaneshR. - While Robert is 100% correct in his answer this a change with C# more so then the compiler itself. The same compiler is used only updated and new assemblies are offered in the case of .NET 4.5 – Security Hound Dec 20 '12 at 18:31
  • 1
    Yes, yes, this explains why when csc.exe is compiling C#5 code it does what it does. What it does NOT do is explain how csc.exe knows that it is compiling C#4 code when being called within VS2010, that's the root of the OP's question. – mletterle Dec 20 '12 at 18:39
  • @mletterle - Its simple. The default language version is always the current version when dealing with Visual Studio. This was always a pain with Visual Studio 2008 when .NET 2.0 and .NET 3.0/.NET 3.5 ( C#2, C#3 ) were both supported by the same version of Visual Studio. C#2 and C#3 I do believe used two different versions of csc.exe if I am not mistaken. – Security Hound Dec 20 '12 at 18:44
  • @Ramhound If you have both VS2012 and VS2010 installed then the current version installed is C#5 and by your logic compiling within VS2010 should produce C#5 behavior. – mletterle Dec 20 '12 at 19:09
  • @mletterle - Visual Studio 2010 by default will use C# 4 syntax. You can set VS 2012 to use C# 4.0 syntax easy enough. – Security Hound Dec 20 '12 at 19:11
  • 2
    @Ramhound This doesn't explain how when handing a text file to csc.exe it knows which syntax is being used, especially when there are no differences in content as in the OP. – mletterle Dec 20 '12 at 19:15
  • @mletterle - Yes...It does...I have explained the reason. The reason for the difference is the behavior of syntax sugar and the resulting CLR code. – Security Hound Dec 20 '12 at 19:28
  • 2
    @Ramhound The same csc.exe executable, located in the same place on disk, is used by VS2010 and VS2012 for compiling different C# versions, with no obvious command arguments that cause this. The fundamental disconnect is that people think the CSharp compiler is separate from the .NET Framework, it IS NOT. The CSC compiler is installed along with the .NET Framework in %SYSTEMDIR%\Microsoft.NET\Framework\\[Version]. Installing .NET 4.5 on top of .NET 4.0 replaces it. – mletterle Dec 20 '12 at 19:31
  • @mletterle - I don't know what to tell you. Unless you have a better explaination of the behavior has witnessed? Seems based on your profile you should know exactly the reason for this behavior. **You don't need to explain how the CSC compiler works been using it since the very first beta.** – Security Hound Dec 20 '12 at 19:38
  • -1 for implying that `` determines which compiler will be used. You can target 4.0 and still use 4.5 compiler features, like CallerFileNameAttribute with the help of `Microsoft.Bcl` nuget package, as long as you are using the 4.5 compiler (which is unavoidable when using `msbuild`, AFAIK). – binki Sep 18 '13 at 19:40
3

Visual Studio uses an in-process compiler, so it knows which version of C# it's using.

As you noted, csc.exe from the command line, on the other hand, uses whatever C# version it's made to compile, so in your case it'll be C# 5.0. Since it's an in-place upgrade (in terms of installation directory), it might break code that relied on the foreach binding being the same across the whole loop (odd, but possible).


NOTE: Old answer for the wrong question: the OP knows this and was testing it from the command line.

The blog post you link to already answers your question. I think this question is related to this one.

It's the compiler that changed, so this:

foreach (int value in values)
{
    // ...
}

used to generate something along the following code:

{
    int value;
    for (/* iteration */)
    {
        value = /* get from enumerator */;
        // ...
    }
}

while the new C# compiler now generates the equivalent of moving the variable to inside the loop:

for (/* iteration */)
{
    int value = /* get from enumerator */;
    // ...
}

This makes a great difference, since closures within the // ... will capture a new value binding in each cycle, instead of sharing the same value binding that used to be declared outside the loop.

The catch is, if you want your code to work correctly for both older and newer compilers, you must declare your own variable inside the foreach loop:

foreach (int value in values)
{
    int newValue = value;
    // ...
}

The current C# 4.0 specification in Visual Studio 2010 says:

(...) 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
  }
}

The C# 5.0 specification in Visual Studio 2012 says:

(...) 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
  }
}
Community
  • 1
  • 1
acelent
  • 7,965
  • 21
  • 39
  • @RobertP, indeed, now I see the real question, and I see you edited your answer. I'll edit mine too. – acelent Dec 21 '12 at 09:41
  • @PauloMadeira It seems that what they did in VS 2005 / 2008 still holds true. Thanks for the answer by the way. – Ganesh R. Jan 03 '13 at 16:51