Prior to Visual Studio 2008, writing applications that targeted different versions of the Microsoft .NET Framework required installation of different versions of the Visual Studio development environment. Each installation of Visual Studio offered a different developer experience and used significant disk space. Moreover, the project file format changed between each version of Visual Studio. As a result, you could end up with multiple versions of a project or solution while developing a component for use in projects targeting different .NET Framework versions.
Visual Studio 2008 was the first version to fully support multi-targeting within a single IDE, which allowed developers to write applications targeting different versions of the .NET Framework (2.0, 3.0 and 3.5) using a single Visual Studio installation. The result? A single, consistent developer experience with reduced disk-space requirements.
Multi-targeting in Visual Studio 2008 worked because each of the available frameworks used the same underlying CLR 2.0. Furthermore, each version of the framework built upon the .NET Framework 2.0 foundation, providing additional functionality through the use of referenced assemblies. Ultimately, all used the .NET Framework 3.5 command-line Visual Basic compiler (vbc.exe).
In this article I discuss the 3.5 and 4 compilers, referring to the compilers installed as part of the respective .NET Framework 3.5 and 4 installations. The 3.5 compiler is the version shipped with Visual Studio 2008 and Visual Basic 9, while the 4 compiler is the version shipped with Visual Studio 2010 and Visual Basic 10.
So let’s take a look at how multi-targeting works in Visual Studio today, and how you should approach multi-targeting in your projects.
In Visual Studio 2008, changing the desired target framework was as simple as selecting the target from a drop-down list in the project properties, as shown in Figure 1. This added or removed specific references required for each framework version and made changing frameworks painless.
Figure 1 Changing the Desired Target Framework in Visual Studio 2008
For command-line compilation it was simply a matter of changing the reference assemblies used.
Some big changes came with Visual Studio 2010, however. The new .NET Framework 4 brings a new version of the CLR. This means the approach taken in Visual Studio 2008 is not practical in Visual Studio 2010. As a result, Visual Studio 2010 uses the version 4 compiler for all multi-targeting, even when targeting previous .NET Framework versions. This allows many of the newer language features to be used for down-targeting and provides a much-simplified development experience.
However, one downside of being able to use Visual Studio 2010 features for down-level targets is that source files may not be design-time compatible if used with earlier versions of Visual Studio. This may be an issue if you’re sharing source code for projects built using different versions of Visual Studio and targeting different .NET Framework versions.
If you keep the projects within Visual Studio 2010 for all design-time work, you’ll have a better experience. You’ll be able to generate assemblies targeting the .NET Framework 2.0 upward using only Visual Studio 2010 and the .NET Framework 3.5 SP1.
Now let’s take a look at an example of a design-time compatibility gotcha. The code in Figure 2 uses both implicit line continuation and the auto-implemented property feature, both introduced in Visual Studio 2010. This code can be compiled to target any framework from 2.0 onward when compiled using Visual Studio 2010. So the generated assembly is runtime-compatible.
Figure 2 Using New Language Features That Will Work in Down-Level Targets
However, take this same source code file and try compiling using either the 3.5 or 2.0 versions of the compiler—you’ll generate the errors shown in Figure 3.
Figure 3 Source Code from Visual Studio 2010 Is Not Design-Time Compatible with Visual Studio 2008
This occurs because the earlier versions of the compiler know nothing about these features and treat this as invalid code. So the source files are not design-time compatible. To make this design-time compatible, you’d have to use only features available in the 3.5 compiler.
Design-time compatibility has implications for Web projects as well. For many Web projects, compilation takes place on the server, and the server, of course, compiles the pages using the target framework compiler installed on the server. So if you have a Web page written in Visual Basic targeting the 3.5 compiler, the page would be compiled on the server using the 3.5 version of the Visual Basic Compiler (vbc.exe). Any use of new Visual Studio 2010 language features would fail as the 3.5 compiler knows nothing about them.
For code developed in Visual Studio 2010, which uses the version 4 compiler, you need a way to identify this requirement at compile time to prevent unexpected errors when the Web page is deployed to the server. You can do this with the /langversion switch, which is used when developing Web projects to generate errors about newer language syntax features that won’t compile on an earlier framework’s compiler. When building ASP.NET project types, this switch is used internally to generate errors if your code uses new Visual Studio 2010 features but you are targeting earlier versions of the framework.
Although the /langversion switch is not used by default for any of the other project types, it can be useful if you want to verify that your source code is design-time compatible with previous versions of Visual Studio.
The multi-targeting user experience in the Visual Studio 2010 IDE is almost identical to that of Visual Studio 2008. It is still controlled from within the project properties, but in a default installation you may not see the earlier target frameworks. To cut down on install size, the Visual Studio team decided not to ship the 3.5 framework in the default installation of Visual Studio 2010. This change means that you would not see these framework options appearing in the Target framework drop-down or New Project dialog box.
To add these additional frameworks, you need to install the .NET Framework 3.5 SP1. You can do this right from the IDE. At the top of the New Project dialog box, you’ll see a drop-down menu for choosing the target framework. If only the .NET Framework 4 is installed, the menu contains a link to download more. If you install any others, however, you’ll see only the .NET Framework 3.5 SP1 on the drop-down menu because Visual Studio only recognizes installation of the .NET Framework 3.5 SP1 here.
Another change has to do with client profiles. These were introduced in the .NET Framework 3.5 SP1 and they let applications use a lightweight version of the framework, which can improve deployments by not requiring the inclusion of server-side pieces of the framework, like ASP.NET. These profiles are available for both 3.5 and 4 framework targets.
As a result, the default profile for various project types has changed. The client project types—Windows, Console, Office and Windows Presentation Foundation (WPF) applications—will default to the client profile. However, for Web applications the default profile will be “full” because of references to libraries that are not deployed with the client profile, such as System.Web.
Class libraries also default to the full profile, but can be easily changed back to a client profile if you’re depending only on references deployed with the client profile. If your class library is set to full profile and is then used in a project with a client profile, it will still work as long as the library doesn’t depend upon references that are not part of the client framework assemblies.
By default, none of the references added to the class library project type require a full profile. However, because they’re deployed with an application, the application deployment profile is the important setting to ensure full application functionality. If your library depends on references outside of the client scope, both the library and the application using it need to employ the full profile.
The version 4 compiler has a number of command-line switches, none of which, unfortunately, controls the target framework so it’s important to understand a little about each of the switches and how they work.
If the .NET Framework 4 is installed, it is possible to build applications using vbc.exe that target earlier versions of the framework without having Visual Studio installed on the build machine. Build scripts that call the command-line compiler directly are often used in larger development environments. If you’re targeting earlier versions from the command line, this requires files installed with the previous framework version you’re targeting, so the best plan is to have both .NET Framework 3.5 SP1 and .NET Framework 4 installed on the machine.
With this in mind, some of the potential switches for multi-targeting are detailed in Figure 4.
Figure 4 Command-Line Build Switches to Control Multi-Targeting
This table provides a quick description of each switch, but in order to actually create a down-targeted compilation, you’ll need to use a combination—there’s no single multi-target switch. For a version 3.5 target, the most important switch is sdkpath, which you can use to specify the 2.0 version of MSCorlib. Then ensure your references point to the correct versions of System.dll, System.core.dll and any other prior target framework assemblies. (These can be found in the %programfiles%\Reference Assemblies\Microsoft\Framework\v3.5 folder.)
You need to specify the noconfig switch to avoid using the default switches in the version 4 of vbc.rsp, which contains the default settings for compilation. Without adding this critical switch, the compiler would add those default 4 references, imports and so on.
Multi-targeted command-line compilation is best demonstrated by an example. Here I’m simply compiling a simple source file, test.vb, to target the .NET Framework 3.5:
vbc.exe /noconfig /sdkpath:D:\WINDOWS\Microsoft.NET\Framework\v2.0.50727 /r:"D:\Program Files\ReferenceAssemblies\Microsoft\Framework\v3.5\System.Core.dll" d:\school\test.vb /out:\school\test.exe
When you understand the switches to compile a 3.5 assembly, targeting 2.0 instead simply involves removing some of the references, such as system.core.dll, that are required for the 3.5 framework. The sdkpath and noconfig switches remain the same:
vbc.exe /noconfig /sdkpath:D:\WINDOWS\Microsoft.NET\Framework\v2.0.50727 d:\school\test.vb /out:\school\test.exe
Once you have the compiled binary, you can employ a tool such as the MSIL Disassembler (Ildasm.exe) or .NET Reflector to look at the versions of the references being used. This lets you determine whether an executable will run on a desired target framework.
Earlier in the article I mentioned that client profiles were the default for most project types. When such applications are deployed, a smaller-footprint framework installation occurs. As part of this deployment you can observe that the command-line compiler is deployed. This is the same version of the compiler that’s deployed with the full framework, but there’s a potential gotcha when trying to compile a simple application on a client profile.
Using this command on a client framework machine would fail because the client frameworks do not ship with a vbc.rsp that contains the default references:
vbc.exe test.vb /out: test.exe
You’d have to either specify all the references and imports statements that would normally be contained in the vbc.rsp file or create your own.
The bare minimum switches required to compile a Visual Basic application on a client framework installation are:
By including these switches you can compile a basic Visual Basic application. However, you should compile applications on a machine
that has the full framework installed.
Mixed-target solutions—class libraries built with the .NET Framework 3.5 used with a client application targeting the .NET framework 4—are supported, but with some caveats. Within the Visual Studio 2010 IDE, if you’re using project references and are accustomed to the experience they provide, you can still get this experience if the target frameworks in the project references use the same version of MSCorlib. Figure 5 shows the MSCorlib versions and supported framework versions.
Figure 5 MSCorlib and Framework Version Compatibility
So if you’re using a class library targeting MSCorlib 2.0 on the .NET Framework 3.5 application, you are still able to use project references. Similarly, a .NET Framework 4 full-profile class library referenced by a .NET Framework 4 client-profile Windows application can have a project reference to the library.
However, if you use a project-to-project reference where a different version of MSCorlib is used, the project reference will be converted into a file reference. This means you’ll need to manually rebuild the solution when you correct errors. The experience will be familiar if you’ve worked with solutions that have multiple referenced projects written in both C# and Visual Basic. You lose some of the convenient features available with project references, such as renaming across projects and automatic background compilation.
The IDE shields you somewhat from what happens behind the scenes during compilation, but not everyone builds from the IDE. The file reference will automatically change back to a project reference if the target frameworks are changed so that both use frameworks with a common version of MSCorlib—so it’s not a true file reference.
What happens if you use a down-target (3.5) class library within a version 4 application? The class library will actually run against the .NET Framework 4. Considerable testing has occurred to ensure that this scenario works at run time with few problems. However, trying to use a 4.0 framework class library on a 3.5 framework application is not supported and will result in a compile-time error if building with either Visual Studio or MSBuild. To use 4.0 framework class libraries in a 3.5 framework-targeted application, however, you would need to down-target the class library to the .NET Framework 3.5.
Keep in mind, though, that with the ability to use the Visual Studio 2010 language features on down-target scenarios, targeting the class library to the .NET Framework 3.5 should not be a big issue. Figure 6 summarizes the new features you can expect to work in down-target projects.
Figure 6 New Visual Studio Features in Down-Target Scenarios
Using the .NET Framework to program against the Microsoft Office object model requires the use of Primary Interop Assemblies (PIAs), which must be deployed to the user’s machine. These assemblies are often very large and deploying them can be a nuisance.
The new type-embedding feature allows these applications to be deployed without requiring PIAs on the user’s machine. It does this by generating embedded interop types that perform the interop calls to the COM library directly. These types are annotated by the compiler in such a way that the CLR treats all embedded interop type instances as equivalent. The compiler will not copy every type in the PIA into your assembly, just the ones you actually use. For more information, see “Type Equivalence and Embedded Interop Types” in the MSDN library (msdn.microsoft.com/library/dd997297(VS.100)).
The functionality of this feature is not supported for down-target scenarios below the .NET Framework 4. Using the Visual Studio IDE, this would not be immediately obvious as setting the reference embedded interop property to true in a down-target scenario results in normal references being used. The user experience for the feature from the IDE is that the assembly will continue to build, but would revert back to the behavior of standard references, which would require PIAs to be deployed.
From the command line, references are normally added using the /reference switch. For embedding, the /link switch is used instead. Attempting to use the /link switch for down-target scenarios results in compile errors.
Here’s an example of a command line embedding types from the Word interop assembly:
D:\Windows\Microsoft.NET\Framework\v4.0.30128\Vbc.exe /imports:Microsoft.VisualBasic,System /link:"D:\Program Files\Microsoft Visual Studio 10.0\Visual Studio Tools for Office\PIA\Office14\Microsoft.Office.Interop.Word.dll" /reference:"D:\Program Files\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0\Profile\Client\System.Core.dll","D:\Program Files\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0\Profile\Client\System.dll" /out:ConsoleApplication16.exe /target:exe Module1.vb
This behavior is important because, by default, COM references added to a project in Visual Studio 2010 set the Embed Interop property to true. So changing the target framework should not result in additional errors, but should provide the benefit of embedded interop types where possible.
Another new feature in Visual Studio 2010 that is not supported for down-target scenarios is dynamic interop because, prior to Visual Studio 2010, the Dynamic Language Runtime (DLR) didn’t exist.
Covariance and contravariance are supported for use with user-defined interfaces. However, Base Class Library (BCL) interfaces for down-level targets are not changed and therefore using the feature with these base classes is not supported. For more information on covariance and contravariance, see the Basic Instincts column in the March 2010 issue of MSDN Magazine (msdn.microsoft.com/magazine/ee336029).
If you have a project or solution created in a previous version of Visual Studio that you open in Visual Studio 2010, you’ll see the standard upgrade dialog box. Visual Studio will make the necessary changes to the project or solution files to work with Visual Studio 2010. However, there are two different actions involved in upgrading your files that could affect multi-targeting.
If you have the .NET Framework 3.5 SP1 installed, the upgrade dialog box will allow the project or solution files to be upgraded to Visual Studio 2010, but the target frameworks specified for the project will remain untouched. So if you’re upgrading a .NET Framework 3.5-targeted application, after upgrade it should still target the 3.5 framework.
If you do not have the .NET Framework 3.5 SP1 installed, you can’t build multi-targets correctly because you need the 2.0 version of MSCorlib and the reference assemblies. The dialog will provide the option to change the target to a version 4 framework or to not upgrade the project. Your best course in this case is to cancel the upgrade, install the .NET Framework 3.5 SP1, then run through the process to upgrade the project again.
By understanding a little more of the implementation details of Visual Basic multi-targeting in Visual Studio 2010, you should be able to write code that produces assemblies that can be deployed on prior versions of the framework using the IDE or command line, but still take advantage of some of the new Visual Studio 2010 features. Although multi-targeting has some caveats, you retain the ability to develop and deploy applications that can’t immediately be upgraded to use the .NET Framework 4.
Adrian Spotty Bowles has developed using every version of Visual Basic and managed to find his way to Redmond, Wash., where he works on the Visual Basic product team as a software design engineer tester focused on the Visual Basic compiler. He is still passionate about Visual Basic and can often be found answering questions in the MSDN Visual Basic forums. You can reach Bowles at Abowles@microsoft.com.
Thanks to the following technical experts for reviewing this article: Kevin Halverson and Beth Massi
More MSDN Magazine Blog entries >
Browse All MSDN Magazines
Subscribe to MSDN Flash newsletter
Receive the MSDN Flash e-mail newsletter every other week, with news and information personalized to your interests and areas of focus.