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?
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'...")
See more on this question at Stackoverflow