Untitled Page

C# 4.0 New Features

Introduction

The main theme of C# 4.0 is dynamic programming.  Objects can be dynamic in the sense that their behaviour and structure may not be known at compile type, i.e., not captured by a static type. For example, the following are dynamic objects:

  • .NET types accessed through reflection.

  • COM objects accessed through IDispatch.

  • Objects with changing structure such as HTML DOM objects.

  • Objects from dynamic languages such as Python or Ruby.

The new features of C# 4.0 therefore fall into four groups:

  1. Dynamic Binding

  2. Names and Optional Parameters

  3. CoVariance and ContraVariance
  4. COM Interop

Dynamic Binding

The key point of dynamic binding is: A dynamic object (i.e., an object declared with dynamic keyword) is assumed at compile time to support any operation, and only at runtime will an error occur if the object did not support a specific operation. The following code shows a scenario where dynamic objects can be used:

// Creates a customer

public object GetCustomer()

{

   return new Customer("Yazan");

}

 

// Call GetCustomer method. GetCustomer return an object and this cannot be changed;

// it may be third party code, or code that cannot be changed because it is public

// and is likely to break other components

Customer c = GetCustomer() as Customer;

if (c != null)

{

  // use the customer object

  c.ProcessOrder();

}

The dynamic keyword tells the compiler that a variable's type can change or that it is now known until runtime. Think of it as using an object without having to cast it. The above code can be rewritten to use dynamic variables:

// Note that there is no need to cast the returned object

dynamic c = GetCustomer() as Customer;

c.ProcessOrder(); 

The following example illustrates the above points:

public void TestDynamicBinding1()

{

    try

    {

        dynamic customer = GetCustomer();

 

        // OK

        string name = customer.Name;

 

        // Error. <>f__AnonymousType0<string,int>' does not contain a definition for 'DoSomething'

        customer.DoSomething();

    }

    catch (Exception ex)

    {

        Trace.WriteLine(ex.Mezssage);

    }

 

}

 

public object GetCustomer()

{

    return new { Name = "Yazan", ID = 1 };

}

Another interesting but highly dangerous use of dynamic variables is being able to use a dynamic variable for different types of data. The following example illustrates:

public void TestDynamicBinding2()

{

    // The following is legal

    dynamic myvar = 1.0;

    myvar = "hello";

 

    // A more meaningful use of the above scenario is when having to cast from int to long.

    // This is no longer needed with a dynamic variable. For example,

    // int n = 1;   // OK

    // n = 10L;     // Error: Cannot implicitly convert type 'long' to 'int'.

    //              // An explicit conversion exists (are you missing a cast?)

    dynamic n = 1;  // OK

    n = 10L;        // OK

}

Note that switching from dynamic to static and static to dynamic is easy:

public class Person

{

    public string Name { get; set; }

}

 

public class Employee : Person

{

    public string ID { get; set; }

}

 

public class Manager

{

    public string Title { get; set; }

}

 

 

public void TestDynamicBinding3()

{

    try

    {

        Employee emp1 = new Employee();

        dynamic  dynEmp = emp1;           // OK. static to dynamic

        Employee emp2 = dynEmp;           // OK. dynamic to static. Works because dynEmp is of type Employee

        Person   prsn = dynEmp;           // OK. dynamic to static. Works because dynEmp is of type Employee

                                          // which dervies from Person

        Manager mgr = dynEmp;             // RuntimeBinderException: "Cannot implicitly convert type

                                          //'CS4Features.Employee' to 'CS4Features.Manager'"

    }

    catch (Exception ex)

    {

        Trace.WriteLine(ex.Message);

    }

}

With a dynamic type, not only method calls can be used, but also field and property accesses, indexers and operator calls and even delegate invocations and constructors can be dispatched dynamically:

dynamic d = GetDynamicObject(...);

d.M(7);              // calling methods

d.M(x: "Hello");     // passing arguments by name

d.f = d.P;           // getting and setting fields and properties

d["one"] = d["two"]; // getting and setting through indexers

int i = d + 3;       // calling operators

string s = d(5,7);   // invoking as a delegate

C c = new C(d);      // selecting constructors 

The role of the C# compiler is simply to package up the necessary information about what is being done to d, so that the runtime can pick it up and determine what is the exact meaning of it, given an actual object referenced by d. Think of it as deferring part of the compiler’s job to runtime. If d implements the special interface IDynamicMetaObjectProvider (previously called IDynamicObject), then d is considered a dynamic object, otherwise d is treated as a standard .NET type and the operation will be resolved using .NET reflection on its type.

Named and Optional Parameters

Named and optional parameters are really two distinct features, but are often useful together. Optional parameters allow you to omit arguments to member invocations, whereas named arguments is a way to provide an argument using the name of the corresponding parameter instead of relying on its position in the parameter list.

Some APIs, most notably COM interfaces such as the Office automation APIs, are written specifically with named and optional parameters in mind. Up until now it has been very painful to call into these APIs from C#, with sometimes as many as thirty arguments having to be explicitly passed, most of which have reasonable default values and could be omitted. Prior to C# 4.0, optional parameters were often implemented using overloaded functions:

// Method specifies all required parameters

private void Run(string name, bool bLog, List<int> lstID)

{

    // Implementation comes here

}

 

// Method specifies only one parameters. Delegates to the first method passing

// in default values for the missing parameters

private void Run(string name)

{

    Run(name, false, null);     // Pass default values for second & third parameters

}

 

// Method specifies two parameters only. Delegates to the first method passing

// in default values for the missing parameters

private void Run(string name, bool bLog)

{

    Run(name, bLog, null);      // Pass default value for third parameter

}

The above can now be written using optional parameters. A parameter is declared optional simply by providing a default value for it. In the following, bLog and lstID are optional and can be omitted in calls:

private void RunWithOptionalParameters(string name, bool bLog = false, List<int> lstID = null)

{

    // Implementation comes here

}

 

public void TestOptionalParameters()

{

    RunWithOptionalParameters("Yazan");         // false and null for second and third parameters, respectively

    RunWithOptionalParameters("Yazan", true);   // null for third parameter

    RunWithOptionalParameters("Yazan", true, new List<int>());

}

But what if you had a method with 20 optional parameters and you only wanted to specify a value for the last parameter? This is where named parameters come handy.

RunWithOptionalParameters("Yazan", new List<int>());            // Error: Argument 2: cannot convert from

                                                                // 'System.Collections.Generic.List<int>' to 'bool'

RunWithOptionalParameters("Yazan", lstID: new List<int>());     // OK.

RunWithOptionalParameters(lstID: new List<int>(), name: "Yazan"); // OK. 

Note the following extra points:

  • Optional and named arguments can be used not only with methods but also with indexers and constructors.

  • A required parameter (one that does not have a default value) cannot appear after an optional parameter.

  • A ref or out parameter cannot have a default-argument.

  • It is an error for a positional argument (argument without an argument name) to appear after a named argument.

Covariance and ContraVariance

 

Consider the following code:

 

public class Person

{

    public string Name { get; set; }

}

 

public class Employee : Person

{

    public string ID { get; set; }

}

 

void DoSomething(Person p)

{

    // Implementation ....

}

 

// Call DoSomething using instances of a base class and a derived class

Person   p = new Person();

Employee e = new Employee();

DoSomething(p);

DoSomething(e);

 

DoSomething accepts an instance of Person class or an instance of a Person-derived class (remember that public inheritance means IS A). But now suppose that DoSomething has the following signature:

void DoSomething(IEnumerable<Person> coll)

{

    // Implementation ....

}

By the same logic, when you call this function, you don't have to pass something typed as IEnumerable. The C# compiler knows it's okay to pass anything that derives from (implements) IEnumerable - say, List.  But what about passing an IEnumerabl<Employee> ? After all, if DoSomething can process a sequence of Person objects, it can surely deal with a sequence of Employee objects! Prior to C# 4.0, the compiler would throw compiler errors and did not support that scenario, i.e., passing IEnumerabl<Employee>  when it expected IEnumerabl<Person>.

 

In summary, the C# compiler understands that it's okay to vary the type of an argument, but doesn't understand that it's okay to vary the type of a generic type parameter. And generic variance just means fixing that: fixing the C# compiler so that it is okay to pass an IEnumerabl<Employee>  to a function that expects an IEnumerabl<Person>.

 

When you vary the type of an argument, you can only vary it in the direction of more derived. Passing an Employee to a function that expects a Person is okay. Passing an Object to a function that expects a Person is not okay. When you vary the type of the generic type parameter to IEnumerable, the same rule applies. A function that can deal with a sequence of Person objects can deal with a sequence of Employee objects but not with a sequence of arbitrary objects.

 

In other cases, as shown below, it turns out that the rule has to be the other way round: you can only vary the type parameter in the direction of less derived. Consider this function which takes an IComparer<T> instead of IEnumerable<T>:

void DoSomething(IComparer<Person> coll)

{

    // Implementation ....

}

If we use the IEnumerable rule and call this function with an IComparer, we have a problem. The function expects to be able to use the IComparer to compare arbitrary Person objects. If it decides to compare a Manager and a Director (both derive from Person just like Employee), our IComparer<Person> will be fail. We can't use derived types after all.

 

On the other hand, suppose we call this function with an IComparer<object>IComparer<object> can compare arbitrary objects, so it can easily cope with the specific requirement of comparing transforms. So we can pass a base type in place of the expected type.

 

So sometimes the rule is that we can only vary in the direction of more derived, and sometimes the rule is that we can only vary in the direction of less derived (more base). How do we know which rule applies in any given case? The answer is that it depends on whether the generic type parameter appears in output or input positions.

 

In IEnumerable, Person appears in an output position - it appears in the return value of GetEnumerator. Now if Person appears in an output position, then any user of the generic type, such as the DoSomething method, expects to be receiving Persons, and knows how to deal with them. So it can certainly deal with derived types such as Employee.

 

In IComparer, Person appears in input positions - it appears as the inputs to the Compare method. Now if Person appears in an input position, then a user of the generic type, such as the Compare method, is going to give us Persons, and expect us to deal with them. So we need to be able to deal with Person s at least, but if we can deal with more things, i.e. a base type, then that's not going to do any harm. To illustrate the point further, consider another example (taken from C# specs document):

List<string> lstStrings = new List<string>();

List<object> lstObjects = lstStrings;

The second assignment generates the following error:

 

Cannot implicitly convert type 'List<string>' to 'List<object>'

 

The second assignment is disallowed because a list of strings is not a list of objects.  The second statement attempted to make lstObjects and lstStrings point to the same list. If that was to succeed, then you could have written:

// If lstObjects and lstString pointed to the same list, then we could insert

// a Customer object and retrieve it as a string! This is a breach of type safety!

lstObjects[0] = new Customer();

string s = lstStrings[0];

The problem generated by the second assignment can be overcome by using IEnumerable<T> interface which has no methods to insert objects into the collection. In fact the only method exposed by IEnumerable<T> is GetEnumerator():

IEnumerable<object> lstObjects = lstStrings;    // OK.

Co- and contra-variance are about allowing assignments such as this in cases where it is safe. So the rule is: if a type parameter appears only in an output position, you can vary it in the more derived direction, and if a type parameter appears only in an input position, you can vary it in the less derived (base type) direction. C# specification refers to variance annotations which are in and out which are only used on type parameters when used with interfaces and delegate types. If the variance annotation is out, the type parameter is said to be covariant. If the variance annotation is in, the type parameter is said to be contravariant. If there is no variance annotation, the type parameter is said to be invariant. For example:

// X is covariant, Y is contra-variant, and Z is invariant

interface ISomething<out X, in Y, Z>

{

    X Foo(Y y);

    Z Bar();

}

CoVariance

 

In .NET 4.0, note the new declarations for IEnumerable<T> and  IEnumerator<T>:

public interface IEnumerable<out T> : IEnumerable

{

    IEnumerator<T> GetEnumerator();

}

public interface IEnumerator<out T> : IDisposable, IEnumerator

{

    T Current { get; }

}

The use of out means that the interfaces become covariant in T, which means that IEnumerable<A> is implicitly reference-convertible to IEnumerable<B> if A has an implicit reference conversion to B. For example, if class Employee derives from class Person, then a sequence of Employees is also a sequence of Persons:

List<Employee> lstEmployee = ...

IEnumerable<Person> lstPersons = ...

 var result = lstEmployee.Union(lstPersons);        // OK.

ContraVariance

In .NET 4.0, note the new declarations for IComparable<T>:

public interface IComparer<in T>

{

    int Compare(T x, T y);

}

The result is that an IComparer<object> can in fact be considered an IComparer<string>. This may be surprising at first, but in fact makes perfect sense: If a comparer can compare any two objects, it can certainly also compare two strings. The interface is said to be “contravariant”.

 

Limitations

Co- and contra-variant type parameters can only be declared on interfaces and delegate types. Co- and contra-variance only applies when there is a reference (or identity) conversion between the type arguments. For instance, an IEnumerable<int> is not an IEnumerable<object> because the conversion from int to object is a boxing conversion and not a reference conversion. User-defined conversions, boxing conversions, unboxing conversions, and so on, cannot be used in variance.

 

COM Interop

 

Consider the following pre- C# 4.0 code:

using Microsoft.Office.Interop;

using Microsoft.Office.Interop.Word;

 

object foo = "MyFile.txt";

object bar = Missing.Value;

object optional = Missing.Value;

 

Document doc = (Document)Application.GetDocument(ref foo, ref bar, ref optional);

doc.CheckSpelling(ref optional, ref optional, ref optional, ref optional);

There are a few problems with the code above. First, you have to declare all your variables as objects and pass them with the ref keyword. Second, you can't omit parameters and must also pass the Missing.Value even if you are not using the parameter. And third, behind the scenes, you are using huge (in file size) interop assemblies just to make one method call. C# 4.0 will allow you to write the code above in a much simpler form that ends up looking almost exactly like 'normal' C# code. This is accomplished by using some of the features already discussed; namely dynamic support and optional parameters.

using Microsoft.Office.Interop.Word;

 

dynamic doc = Application.GetDocument("MyFile.txt");

doc.CheckSpelling();