A great new feature of Visual Studio 2008 is multi-targeting, which allows VS 2008 to compile for .NET 2.0, 3.0, or 3.5 simply by changing a project property.
You might be thinking, now I don’t have to keep Visual Studio 2005 and 2008 installed. I can just use Visual Studio 2008 for all my projects! Well, yes, but with one big proviso. Code targeting .NET 2.0 and written in VS2008 may only compile in VS2008! The reality is that multi-targeting changes Intellisense, the project templates, and the assemblies that you’re offered, but your code is still compiled using the C# 3.0 or VB9 compilers regardless of which .NET Framework version you target. Compiling a project targeting .NET 2.0 using VS2008 results in this output:
—— Build started: Project: Multitargetting, Configuration: Debug Any CPU ——
C:\Windows\Microsoft.NET\Framework\v3.5\Csc.exe /noconfig /nowarn:1701,1702 /errorreport:prompt /warn:4 /define:DEBUG;TRACE /reference:C:\Windows\Microsoft.NET\Framework\v2.0.50727\System.Data.dll /reference:C:\Windows\Microsoft.NET\Framework\v2.0.50727\System.dll /reference:C:\Windows\Microsoft.NET\Framework\v2.0.50727\System.Xml.dll /debug+ /debug:full /filealign:512 /optimize- /out:obj\Debug\Multitargetting.exe /target:exe Program.cs Properties\AssemblyInfo.cs
Compile complete — 0 errors, 0 warnings
Multitargetting -> c:\dev\Examples\Multitargetting\Multitargetting\bin\Debug\Multitargetting.exe
========== Build: 1 succeeded or up-to-date, 0 failed, 0 skipped ==========
Note the path to csc.exe – C:\Windows\Microsoft.NET\Framework\v3.5. We’re using the C# 3.0 compiler that ships with .NET 3.5. (Don’t get me started about the renaming of WinFX to .NET 3.0 again. My friends at Microsoft already know how I feel about that one.) Generally this doesn’t matter because .NET 3.0 and .NET 3.5 are additive libraries to .NET 2.0. .NET 2.0, 3.0, and 3.5 all run on the CLR that shipped with .NET 2.0. (I originally read about this in Dustin Campbell’s C#2.5 post.)
So how do we get all the crazy goodness of lambda expressions, anonymous types, LINQ, extension methods, and more? In .NET 3.0, WCF, WPF, WF, and CardSpace were all just additive libraries on top of .NET 2.0. In .NET 3.5, the new features can be divided into two categories – additive libraries and compiler enhancements.
Feature |
Library |
Compiler |
implicitly-typed locals (var) |
|
X |
lambda expressions |
|
X |
automatic properties |
|
X |
anonymous types |
|
X |
object and collection initializers |
|
X |
LINQ |
X |
X |
Expression Trees |
X |
X |
Extension Methods |
X* |
X |
* See below
Any feature not requiring library support is just syntactic sugar provided by our compilers. If you look under the covers using Lutz Roeder’s Reflector, you’ll see that the compilers are generating the same old CLR 2.0-compatible MSIL as they always were.
Implicitly-Typed Locals
Let’s take a look at implicitly-typed locals using the “var” keyword:
var foo = "Hello, World!";
Reflector says! (Harkening back to Family Feud and Richard Dawson.)
string foo = "Hello, World!";
The compiler was able to infer the type of “foo” based on its usage. No magic MSIL instruction to infer type. It’s all in the compilers.
Lambdas
Let’s take a look at our new swanky lambda expressions:
Action<string> display = msg => Console.WriteLine(msg);
Reflector says!
Action<string> display = delegate(string msg) { Console.WriteLine(msg); }
Once again, it’s just compiler magic turning the terse lambda expression (that funky “params => body” syntax) into the anonymous delegate syntax that we’ve known and loved since .NET 2.0. (To be honest, that’s not quite what Reflector says. The C# compiler actually caches the anonymous delegate in a static field for performance reasons, but the code above is close enough for purposes of this discussion.)
Automatic Properties
What about automatic properties?
public string Hello { get; set; }
Reflector says!
[CompilerGenerated]
private string <Hello>k__BackingField;
[CompilerGenerated]
public string get_Hello()
{
return this.<Hello>k__BackingField;
}
[CompilerGenerated]
public void set_Hello(string value)
{
this.<Hello>k__BackingField = value;
}
Once again, more compiler magic.
Anonymous Types
var position = new { Lat=42, Long=42 };
Reflector says!
[CompilerGenerated, DebuggerDisplay(@"\{ Lat = {Lat}, Long = {Long} }", Type="<Anonymous Type>")]
internal sealed class <>f__AnonymousType0<<Lat>j__TPar, <Long>j__TPar> {
// Compiler generated goo omitted for clarity
}
Compiler magic.
Object/Collection Initializers
Object and collection initializers allow us to create and initialize objects/collections in a single line. They only require that the object have a parameterless constructor.
Program program = new Program { Hello="World" };
Reflector says!
Program <>g__initLocal0 = new Program();
<>g__initLocal0.Hello = "World";
Program program = <>g__initLocal0;
The C# 3.0 compiler is changing our object initializer into a plain old object instantiation via the parameterless constructor followed by a bunch of property sets. Nothing else to see here. Move it along…
LINQ, Expression Trees, and Extension Methods
Each of these features requires library support from System.Core, a new assembly that ships with .NET 3.5. Without System.Core installed in the GAC, your code isn’t going to run. So you can’t use these features without .NET 3.5 installed on the client.
The one exception is extension methods. If you try to compile an extension method while targeting .NET 2.0, you will receive the following compile error:
Cannot define a new extension method because the compiler required type ‘System.Runtime.CompilerServices.ExtensionAttribute’ cannot be found. Are you missing a reference to System.Core.dll?
As noted by Jared Parsons here, you can simply define the attribute yourself:
namespace System.Runtime.CompilerServices {
[AttributeUsage(AttributeTargets.Method)]
public class ExtensionAttribute : Attribute {
}
}
Extension methods now compile while targeting .NET 2.0.
N.B. If you target .NET 3.5 as well as .NET 2.0 with this code, you’ll end up with a duplicate definition warning under .NET 3.5 as noted by Dustin Campbell here. If you’re really going to use this trick, you should probably wrap the class in some conditional directives that omit the definition when compiled under .NET 3.5.
Multi-targeting Proviso
What about that proviso I mentioned at the beginning of this post? If everyone on your project is using VS 2008 and targeting .NET 2.0, then all is good. You can use all the new syntactic sugary goodness that the C# 3.0 compiler (and VB9 compiler – if you swing that way) provides for you. If some team members are still using VS 2005, you need to be careful to not introduce C# 3.0 language constructs as VS2008 will not provide feedback that you are using them even if you target .NET 2.0. The new constructs are sufficiently different enough that it’s unlikely that you’ll mistakenly use a lambda expression or automatic property, but it’s something to keep in mind. Besides your CruiseControl.NET server is using a NAnt script that compiles with the C# 2.0 compiler to keep everyone honest, right? If not, this is yet another reason you should set up a CI server for your project today…