C# Corner

Target Multiple .NET Frameworks

Many developers have the misconception that targeting older versions of the Framework means you can't use any of the new C# language features. It's time to dispel that myth.


Targeting multiple .NET platforms is a subject that confuses many developers. For example, many developers mistakenly believe that if you target older versions of the platform, you must forego all the new language features. Yes, you must accept some limitations, but you don't have to give up all the new language features you like. Targeting multiple versions of the Framework is a great way to ensure that you and your peer developers can use Visual Studio 2008 even when one of your target environments must support an older version of the Framework. More than that, it doesn't stop you from using the features in the C# 3.0 language. On the downside, there are some nuances that can crop up when using the newer language features if you're targeting an older version of the Framework. I'll walk you through some of these nuances, explaining what capabilities are in bounds, which require care, and which ones you can't use when you have to target an older version of the Framework.

Multitargeting is a project-level feature. To select your target version of the Framework, right click on the project node and select properties. You can pick any of the 2.0, 3.0, or 3.5 versions of the Framework. In this article, I'm concentrating on the differences between various language features and how you can make use of the newer language features even though you're targeting an older version of the Framework. Obviously, if you're targeting the 2.0 Framework, none of the 3.0 libraries -- Windows Presentation Foundation (WPF), Windows Communication Foundation (WCF), or Windows Workflow Foundation (WF) -- are available to use. In general, other libraries that rely on .NET 2.0 and 3.0 share the same limitations. (Space considerations prevent me from exhaustively discussing the differences between the 2.0 and 3.0 Frameworks.)

The most important consideration when multitargeting is to use straight language features versus the library features. Of course, there are some subtleties. You can use any of the language features in C# 3.0, provided they don't access the 3.5 libraries. This is also true when using C# 3.0 when you're targeting the 2.0 Framework:

var foo = 27;

That's really no big deal, but it might lead you to try more. Object and Collection initializers work even when you target the 2.0 Framework:

List<int> sample = new List<int> 
{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

You can even use lambda expressions:

sample.RemoveAll(n => n % 3 == 0);

Lambda expressions mean you can apply those techniques to your own methods. You can create any of your own methods that take delegates, knowing that clients can take advantage of the easier lambda expression syntax to use your types.

You can create and use Anonymous types, though without LINQ support, there's less you can do with them than you might think.

Obviously, you can't use LINQ libraries. LINQ to SQL, LINQ to XML, and Entity Framework are all part of the .NET 3.5 library, so those are off limits if you've targeted an older version of the Framework.

Extension methods are decorated with the System.CompilerServices.Extension attribute, which is declared in System.Core.dll. That's not available in earlier versions of the .NET Framework, so you can't create extension methods when you're targeting an earlier version of the Framework.

I mentioned there are some nuances that you need to keep in mind when you target earlier versions of the Framework. Take a moment and consider what would happen if you tried to execute this code snippet:

People folks = new People() {
    new Person {FirstName = "Joe", LastName = "Smith"},
    new Person {FirstName = "Bob", LastName = "Jones"},
var foo = from f in folks
          select f.FirstName;

What would happen depends on what methods are defined in the People class. The select keyword -- in fact, the entire query-pattern syntax -- maps to specific methods. The compiler then translates those keywords into method calls. If a candidate method is in scope, the query will compile, even if you're targeting the 2.0 Framework.

This is where it gets a bit complicated so let's take a close look at another example. The select clause in the snippet just described looks for a Select method in scope. The People class contains a possible Select method:

public IEnumerable<TResult> 
    Select<TResult>(Func<TResult> selector)
    foreach (var peep in storage)
        yield return selector(peep);
// elsewhere:
public delegate TResult Func<TResult>(Person id);

The select member method in People satisfies the query-expression pattern for the case where the sequence is of type People so the range variable must be of type Person. I had to define a new version of Func, because Func<TResult> is not available in the .NET 2.0 Framework.

I intentionally chose a simple example because implementing the entire query-expression pattern is a large exercise, and I'm not advocating it if you still have to support the 2.0 Framework. This amount of work would dwarf the amount of work necessary to rollout the 3.5 Framework in almost any customer base. However, it's important to understand that the query expression syntax is shorthand for method calls.

According to the C# spec, the Select method should have a signature of the form:

public C<T> Select<U>(Func<T,U> selector)

That's a mouthful of type parameters, so let's start with a concrete example. The select statement above looks for a method that has the form:

public IEnumerable<string> Select(
    Func<string, Person> selector);

The Select method in People does match that pattern. Therefore, the select compiles. The compiler translates it into this method call, which makes the method call a little easier to understand:

var foo = folks.Select(person => person.FirstName);

What you want to remember from this discussion is that the query expressions are nothing more than shorthand for method calls. Where those method calls are present, the query syntax will work fine when targeting the .NET 2.0 Framework. However, to write code like this in a general way is to create far more work for yourself than would normally be justified.

Changes in Behavior
Your development box must have .NET 3.5 if you're running Visual Studio 2008, as well as the C# 3.0 compiler. This combination can lull you into a false sense of security. Your testing on your development machine will not uncover any incompatibilities between the 2.0 and 3.5 Frameworks. The Visual Studio IDE will help you with almost all of those issues, because the IDE will tell you if you're trying to include any assemblies that were added in a newer version of the .NET Framework. Microsoft tried hard to ensure that there were no breaking changes, but a few things still snuck in.

You can find one example of this particular issue in the INotifyPropertyChanging interface. In .NET 3.5, the System.ComponentModel namespace added the INotifyPropertyChanging interface. This interface is new and corresponds to the INotifyPropertyChanged event. The problem -- for a multitargeting application -- is that this event will compile fine when targeting the 2.0 Framework. It's in an existing namespace and an existing assembly. In fact, it will run on your development machine, but it will fail when you move to a machine where only the 2.0 Framework is available. Fortunately, that's the only example of this kind of problem that I've encountered so far, but it's the kind of problem you will only find by testing on a machine where the target framework -- and only the target framework -- is installed.

I've always viewed multitargeting as a short-term solution to enable developers to move forward, while still supporting customers that aren't ready to move to the latest distributions. If you view it in this light, you can safely move development staff to the latest version of Visual Studio and take advantage of some of the newer language features while still supporting those customers that must run on older legacy systems. The goal of such a move should be to get customers and developers looking at the higher productivity available in the newer versions of the .NET Framework and to ultimately drive toward targeting the newest Framework.

About the Author

Bill Wagner, author of Effective C#, has been a commercial software developer for the past 20 years. He is a Microsoft Regional Director and a Visual C# MVP. His interests include the C# language, the .NET Framework and software design. Reach Bill at wwagner@srtsolutions.com.

comments powered by Disqus
Upcoming Events

.NET Insight

Sign up for our newsletter.

I agree to this site's Privacy Policy.