Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generated code differs when compiling solution in VS2010 or via MsBuild command line

I have the following C# code:

foreach (var x in m_collection)
{
    m_actionCollection.Add(() =>
    {
        x.DoSomething();
    });
}

If I compile the solution in VS2010 the following code is generated (decompiled with IlSpy):

foreach (var x in m_collection)
{
    m_actionCollection.Add(() =>
    {
        x.DoSomething();
    });
} 

If I compile the solution via MsBuild command line the following code is generated:

using (List<X>.Enumerator enumerator = this.m_collection.GetEnumerator())
{
    while (enumerator.MoveNext())
    {
        X x = enumerator.Current;
        m_actionCollection.Add(() =>
        {
            x.DoSomething();
        });         
    }
}

The project file contains

<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>

and the MsBuild command line looks like the following: C:\Windows\Microsoft.NET\Framework\v4.0.30319\MsBuild.exe Solution.sln /property:Configuration=Debug /property:Platform=x86 /maxcpucount /nologo /toolsversion:4.0

so I assume the compiler is the same, but it isn't...

What must be done/checked to have exactly the same environment for building via MsBuild command line and in VS 2010?

like image 227
Harry13 Avatar asked Feb 18 '14 09:02

Harry13


Video Answer


1 Answers

I suspect you're seeing the difference between the C# 5 compiler (invoked via msbuild) and the C# 4 compiler (in VS2010). I'm somewhat surprised that msbuild is using the C# 5 compiler, but it appears to be...

The rules for how a foreach iteration variable is captured changed between C# 4 and C# 5. In C# 5, each iteration of the foreach loop effectively captures a different variable. In C# 4, all iterations capture the same variable.

This means that your original code is effectively broken in C# 4, but is fine in C# 5. In C# 4, you'll fine that if you execute all the actions in m_actionCollection afterwards, it will call DoSomething() on the last item of m_collection lots of times (m_collection.Count times). In C# 5, it will call DoSomething once on each item of m_collection.

It doesn't help that it's not clear what version of the language ILSpy is effectively decompiling to :(

What must be done/checked to have exactly the same environment for building via MsBuild command line and in VS 2010?

I would strongly suggest that the simplest approach would be to move to VS2012 or VS2013 instead.

You can check that by looking at the msbuild logs, and seeing which version of csc.exe is being used for the CoreCompile step. Once you know which compiler binary is being used, you can run just the compiler from the command line to tinker as you see fit.

When I run msbuild with /toolsversion:4.0 it uses c:\Windows\Microsoft.NET\Framework\v4.0.30319\csc.exe, which itself reports:

Microsoft (R) Visual C# Compiler version 4.0.30319.33440 for Microsoft (R) .NET Framework 4.5

That appears to be a C# 5 compiler, despite the name. I suspect that's due to .NET 4.5 being an in-place upgrade.

The simplest way of checking which compiler you're really using is to try to include an async method. For example, here's a complete class:

class Test { static async void Foo() {} }

If compiling that gives a warning CS1998 ("This async method lacks await operators...") then it's using a C# 5 compiler. Older compiler will give an error (probably "Invalid token 'void'...")

like image 138
Jon Skeet Avatar answered Oct 08 '22 06:10

Jon Skeet