Visual C# version 4.0 offers new features that make it easier for you to work in dynamic programming scenarios. Besides dynamic programming, you have support for optional and named parameters, better COM interop support, and contra-variance and covariance. This article will show you how each of these features work and provide suggestions of
how they can be applied to help you be more productive.
To help you follow the path of C#, this article looks at the history of C#, today’s use of C#, and helps you understand the future of C# and what the language intends to provide for you. After you understand the theme of C# 4.0, you’ll learn about the new features of C# 4.0. Finally, this article will show you how to create a dynamic object of your own with late-bound calls to dynamic methods based on conventions.
C#: Then and Now
The previous major versions of C# were 1.0, 2.0, and 3.0. There was a minor version 1.1 in April of 2003, but it didn’t significantly change the theme of the 1.0 release. I’ll discuss these versions in the following sections.
Microsoft first announced C# on June 16th 2000. It was the first high-level programming language that was built specifically to target the .NET Common Language Runtime. C# 1.0 grew its heritage from C++, but borrowed features from languages such as Delphi, Java, and others. In C# 1.0, Microsoft planned to provide an object-oriented, component-based language that was very simple to use. When Microsoft released C# 1.0 to manufacturing on February 13th 2002, it was an immediate hit and steadily grew in popularity.
When C# 2.0 rolled around, Microsoft finally added all of the features that should have been in C# 1.0. For example, generics was huge and is an important part of .NET development today. C# 2.0 also introduced anonymous methods, iterators, and nullable types. An interesting addition to C# 2.0, nullable types was a pre-cursor feature for what was coming in the next version, focusing on data.
Most developers work with data, which was the primary theme of C# 3.0. The largest C# 3.0 language addition was Language Integrated Query (LINQ). Most other language features added in C# 3.0 were primarily to support LINQ, but the new features; including implicitly typed local variables, anonymous types, object and collection initializers, lambdas, and extension methods, can have value on their own in development that doesn’t involve LINQ.
The next version of C# will be 4.0, which is the focus of this article. C# 4.0 will primarily focus on dynamic programming. The following sections of this article explain the dynamic programming features of C# 4.0 as well as other new features such as optional/named parameters and covariance/contravariance.
Why Dynamic Programming?
The dynamic programming story in C# can fall into fulfilling categories of need in the way of multiple-language integration, simpler reflection, access to HTML DOM in Web scenarios, and easier COM interop. Some of these categories of need might not apply to your particular situation, and that’s okay because there isn’t anything that says that you have to use a language feature just because it’s there. Therefore, I’ll give you an idea of how someone with a specific need might find value in C# 4.0 dynamic programming.
C# 4.0 will primarily focus on dynamic programming.
Most C# developers use multiple tools in a single application to accomplish complex tasks. If you’re writing WPF desktop applications, you’re using C# and XAML. It is quite possible that you might find some open source code that solves a problem, but it might be written in another language such as VB or F#. One of the benefits of .NET since its inception is the ability to have cross-language interoperability and the runtime is even called the “Common Language” Runtime (CLR). In recent years, Microsoft has created dynamic languages, such as IronRuby and IronPython, but developers don’t have an easy way to perform interop with dynamic languages. If you have this need, then you’ll welcome the ease with which C# dynamic programming makes interop between C# and dynamic languages possible.
When performing reflection to run a method on an object, there are several hoops to jump through, including obtaining a reference to an object type, getting a reference to a member info object, determining the type of bindings to use, and then invoking the member. While reflection has an undeniable coolness factor, it still feels like a hack and that’s where C# 4.0 dynamic methods can help. Later in this article, I’ll show you how to just call the object member.
If you write Silverlight applications, you might have the need today or in the future to access the HTML DOM containing your Silverlight control. C# dynamic programming makes this task easier.
Performing COM interop with C# has always been cumbersome; partly because of the need to write extra syntax for conversions, optional parameters, and more. This has left some C# developers with a touch of VB envy because VB has easier COM interop support. One of the purposes of dynamic programming in C# is to help the C# programmer write cleaner syntax in COM interop scenarios.
I’ve spent some time explaining some of the potential benefits of dynamic programming because it’s so new that the value might not jump out at you immediately. In following sections, I’ll share the how so that you can match it up with the why that you might care about. Before diving into dynamic programming, let’s look at a couple other new features of C# 4.0, optional and named parameters.
Optional Parameters
If you’re of the same mind, then you’ll probably be pleased that C# 4.0 supports optional parameters.
For many C# developers, the long wait for optional parameters is over with C# 4.0. An optional parameter lets you provide a default value and the caller has a choice of whether or not they want to provide an argument. In current and earlier versions of C#, you could simulate optional parameters like this:
// Overload with no parameter
public static string SayHello()
{
return "Hey, You!";
}
// Overload with normal parameter
public static string SayHello(string name)
{
return "Hey, " + name + "!";
}
The SayHello method above is overloaded with both an empty parameter list and a parameter list with a single string parameter. To the user of this code, the name parameter appears to be optional and both of the following calls work fine:
// Name is optional
var greetYou = SayHello();
// Can provide name if you want
var greetJoe = SayHello("Joe");
If you had methods with more parameters and wanted to provide a more flexible coding experience, you would provide more overloads. Many developers have said that this is cumbersome and leaves more code to maintain. If you’re of the same mind, then you’ll probably be pleased that C# 4.0 supports optional parameters. The snippet below shows an optional parameter, replacing the previous two overloads:
// Method with optional parameter
public static string SayHello(string name = "You")
{
return "Hey, " + name + "!";
}
As shown in the previous snippet, the syntax to call an optional parameter requires assigning a default value to the parameter. In the listing above, "You" will be assigned to name if the caller does not provide a value.
A feature related to optional parameters is named parameters, which is discussed next.
Named Parameters
One of the primary purposes of named parameters is that they resolve ambiguity between parameters. For example, the following method contains two parameters that are strings:
public static void AddClubMember(
string name,
string email = "",
DateTime? dateJoined = null)
{
// Not yet implemented
}
The types of the first two parameters of the AddClubMember method above are both string, except that name is not optional, but email is optional. This example also demonstrates that you can default parameters to null.
AddClubMember(
email: "joe@dot.net",
name: "Joe",
dateJoined: DateTime.Now);}
The call to AddClubMember above uses named parameters where the name is the same as the parameter name in the method declaration with an appended colon. Notice that I’ve shuffled the parameter order to demonstrate that you can change the order of parameters.
Named parameters are particularly useful when you have multiple optional parameters of the same type.
Named parameters are particularly useful when you have multiple optional parameters of the same type. In that case, you name the parameter that you want to set, telling C# which parameter your argument matches. The code below demonstrates a problem where named parameters are necessary:
AddClubMember(
"joe@dot.net",
dateJoined: DateTime.Now);}
If you recall, the first parameter of the AddClubMember method is name, a required parameter of type string. Since the first argument above is a string, C# will match that argument to the name parameter. Clearly, the value above is an email address, which presents you with a logical error that won’t be detected at either compile time nor run time when that line executes.
Dynamic Basics
Earlier in the article, you read about the benefits of dynamic programming and how you can use it in various scenarios. The examples in this section transition from the concept of dynamic programming and actually show you how to write dynamic code and demonstrate the essentials of what you need to know about the mechanics of implementing dynamic code.
At compile time, C# will allow you to perform any operation you want on a dynamic type variable.
Before getting into dynamic code, I’ll need to set up some supporting code and explain the code so you can follow along. Listing 1 has a set of three classes: Airplane, Bird, and Tree. Notice that Airplane and Bird have fly methods, but Tree does not. Also, notice that none of these classes implements an interface or a common base class, meaning that there isn’t any code that creates a strongly typed relationship among these classes. Later, I’ll show you how to access these classes as dynamic types and the implications of doing so.
The code below shows a method that will return an instance of Bird:
public object GetFlyingThing()
{
return new Bird();
}
Notice that the return type is object, which is helpful to simulate the conditions when you might receive a reference to an object and don’t know exactly what that object type is. Perhaps the object came from a third-party library or from a reference to a library written in a dynamic language, such as IronRuby or IronPython.
The only thing you will know about the object returned from GetFlyingThing is that it will have a Fly method. Since the reference is object, C# has no way to know at compile time whether that object has a Fly method, but you know because you read the documentation for the GetFlyingThing method. In C# 3.0, you use Reflection to invoke the Fly method on the object, but in C# 4.0, you can call Fly on the object like this:
dynamic flyingThing = GetFlyingThing();
flyingThing.Fly();
The previous example assigns the reference returned from GetFlyingThing, which is type object, to flyingThing, whose type is dynamic. The dynamic type is the same as an object type, except that you can do things to a dynamic type. At compile time, C# will allow you to perform any operation you want on a dynamic type variable. Binding for a dynamic type occurs at runtime. The example above calls Fly on the dynamic type variable flyingThing, which will run the Fly method of Bird at runtime. If you’ve ever used reflection to work with members of an arbitrary object, you’ll be able to appreciate the simplicity of the example above.
If the method you call on a dynamic object doesn’t exist, you do not receive a compiler error. Instead, you receive a run-time exception. The following example shows a scenario that will cause a run-time exception:
public IEnumerable<object> GetFlyingObjects()
{
yield return new Airplane();
yield return new Bird();
yield return new Tree();
}
The iterator method above returns an instance of Airplane, Bird, and Tree. If you recall from Listing 1, Tree doesn’t have a Fly method. Therefore, the following code generates a run-time exception:
foreach (dynamic flyThing in GetFlyingObjects())
{
flyThing.Fly();
}
As the code iterates through each value, all will be fine until GetFlyingObjects yields the Tree instance. Fortunately, the run-time exception message will be the exact same message that you would see at compile time if flyThing was not dynamic.
Dynamic with COM Interop
What I would like to do next is pull together some of the previous C# 4.0 concepts and show how they make essential tasks, such as COM interop, much easier. I should note that VB has provided excellent COM interop support for several years. Now, C# is catching up and, as you are about to see, you’ll now have the same productive experience as the VB developer.
To see dynamic programming benefits, Listing 2 contains a method that doesn’t use dynamic programming, ExcelInteropOld, and a method that does use dynamic programming, ExcelInteropNew. An immediate visual scan of the code from each method should give you an idea of how much simpler the dynamic code is. However, I still want to point out a few important items to demonstrate this. The code snippet below shows a typical call to Excel to add a new Worksheet:
var wkBook =
excelApp.Workbooks.Add(Missing.Value);
var wkSheetData =
(Excel.Worksheet)wkBook.ActiveSheet;
As shown previously, current versions of C# require that you use Missing.Value, a static readonly field in the System.Reflection namespace, which is a placeholder for optional values. Since return types in COM interop are object, you need to explicitly perform a conversion as in the ActiveSheet assignment. The following example shows how dynamic programming and optional parameters simplify the previous code:
dynamic wkBook = excelApp.Workbooks.Add();
Excel.Worksheet wkSheetData = wkBook.ActiveSheet;
By explicitly setting the type of wkBook to dynamic, you can read the ActiveSheet property, assigning the result to the Excel.Worksheet type. Another example of how optional parameters clean up code is the SaveAs monstrosity that follows:
wkBook.SaveAs(
"myfile.xls", Missing.Value, Missing.Value,
Missing.Value, Missing.Value, Missing.Value,
Excel.XlSaveAsAccessMode.xlShared,
Missing.Value, Missing.Value,
Missing.Value, Missing.Value, Missing.Value);
Only two arguments are provided in the example above, with the rest being Missing.Value. Now look at a simpler version with optional parameters:
wkBook.SaveAs(
"myfile.xls",
Excel.XlSaveAsAccessMode.xlShared);
If you’ve ever spent more time figuring out which position a parameter should be in for a method such as SaveAs, you’ll appreciate the simplicity from the previous re-write with optional parameters. Also, you could use named parameters, and might be tempted to in the case of the second argument, but there is only one argument in the parameter list of type Excel.XlSaveAsAccessMode, so naming it isn’t necessary.
Covariance and Contravariance
The basic reasoning of covariance and contravariance can be seen through arrays today, where you can assign between arrays of base and derived types. Look at this example of covariance and contravariance in arrays:
string[] strings = new string[]
{ "one", "two", "three" };
object[] objects = strings;
string[] strings2 = (string[])objects;
In the previous code, assigning a string to an object from specific type to a more general type is called covariance. In contrast, assigning an array of objects to an array of strings, from general type to a more specific type, is referred to as contravariance.
If you’ve ever spent more time figuring out which position a parameter should be in for a method such as SaveAs, you’ll appreciate the simplicity from the previous re-write with optional parameters.
So, if you have covariance and contravariance in arrays, you should be able to have the same semantics with collections. However, in C# 3.0 that is not possible. C# 4.0 introduces covariance and contravariance. This example shows covariance and contravariance on collections:
IEnumerable<string> strings3 = strings;
IEnumerable<object> objects3 = strings3;
IEnumerable<string> strings4 =
(IEnumerable<string>)objects3;
The previous code uses IEnumerable instead of array, demonstrating covariance and contravariance in C# 4.0.
The collection types in .NET 4.0 will support covariance and contravariance. However, there are developers, perhaps you, who need to build their own generic collections and would like for them to be covariant and contravariant also. The following examples use single types, rather than collections, to simplify the code.
Listing 3 demonstrates how to define an interface to support contravariance. Notice the in modifier on the type parameter, T. You modify the type parameter of a generic collection, delegate, or interface with an in modifier to allow safe contravariance support. If the contravariance is safe, you don’t need to use a cast operator. The cast operator in the previous code is required because IEnumerable is not safely contravariant. Here’s a demo on how to use the IHaveBinaryEquality interface in contravariance:
However, there are developers, perhaps you, who need to build their own generic collections and would like for them to be covariant and contravariant also.
IHaveBinaryEquality<Vehicle> vehCmp =
new Vehicle();
IHaveBinaryEquality<Truck> truckCmp = vehCmp;
Listing 3 shows how Truck derives from Vehicle. Since the IHaveBinaryEquality interface is safely contravariant, you can assign IHaveBinaryEquality<Vehicle> to a variable of type IHaveBinaryEquality<Truck> without a cast operator.
Similarly, you can use the out modifier on a type parameter to indicate safe covariance. The IUseGas interface that follows is safely covariant:
interface IUseGas<out TGasUser> { }
class GasEngineVehicle<TGasUser> :
IUseGas<TGasUser> { }
You can tell that IUseGas is safely covariant because it has an out modifier on its type parameter. The following example shows how to use the types in a safely covariant way:
IUseGas<Car> gasCar = new GasEngineVehicle<Car>();
IUseGas<Vehicle> gasVehicle = gasCar;
Recall that GasEngineVehicle implements IUseGas. If you wanted to draw a parallel between this example and collections, GasEngineVehicle<T> would be to List<T> as IUseGas<T> would be to IEnumerable<T>. I just needed to show simple code to demonstrate the in/out type parameter modifier. Since IUseGas is safely covariant, you can assign gasCar to gasVehicle.
C# and VB Language Parity
For a number of years, C# and VB have diverged in their feature set, where each language began having their own unique benefits. For example, VB’s LINQ and COM interop support are excellent. In C#, you generally have a more terse syntax and don’t have annoyances such as line continuations and can write multi-line lambdas. The list could go on, but in C# 4.0, the list of differences will begin to shrink.
For .NET 4.0, Microsoft combined the C# and VB teams into a common group and established a goal of bringing the feature sets of both C# and VB together.
For .NET 4.0, Microsoft combined the C# and VB teams into a common group and established a goal of bringing the feature sets of both C# and VB together. If you’re a consultant who actively changes languages based on the job, this will be welcome news. However, if you tend to program in only one language, you might be wondering if you prefer for Microsoft to invest their resources into the features of your language that you’ve been wanting for some time.
While features such as optional parameters and dynamic coding evoke strong passion, I’ve noticed that most developers are pretty neutral on their opinion of C# and VB language parity. Time will tell.
Summary
I started this article with a high-level introduction to the new features of C# and potential benefits that depend on the type of work you’re doing. You’ve seen how optional and named parameters can make coding easier by eliminating additional code that would be implemented as overloads in earlier versions of C#. The dynamic coding examples showed you the essential elements of creating and using dynamic types. COM interop is a place where you see many of the new features of C# come together as you saw in the Excel automation demo. In addition to array covariance and contravariance, you learned how to use and implement covariance and contravariance with generic types. Finally, C# and VB are coming together in terms of feature set, ensuring that one set of developers isn’t left out of important functionality in the future.