C# 4.0 New Features - Straight to the Point (Part 2)

In the previous article we discussed Optional Parameters, Named Parameters and Overload Resolution. These features were missing in the .NET framework prior to 4.0 which has limited the usage of C# in some of the areas or considered VB.NET simpler when the application only needs these kinds of features. Now let us focus on the other 3 new features.

  1. Covariance and Contravariance
  2. Dynamic
  3. New Compiler options

Covariance and Contravariance

According to Wikipedia, the following is the definition of Covariance and Contravariance:

Covariance and Contravariance refers to the ordering of types from narrower to wider and their interchangeability or equivalence in certain situations (such as parameters, generics, and return types).

Covariant : converting from a specialized type (Cats) to a more general type (Animals): Every cat is an animal.

Contravariant : converting from a general type (Shapes) to a more specialized type (Rectangles): Is this shape a rectangle?

Invariant : not able to convert.

In simple terms, if there are two types T and U:

An operation that works with types "T" and "U" and if it keeps the relationship the same is called Covariant. If it inverts the relationship it is called Contravariant.

Let us check how Covariance works with the following code and if you run it will give you an error at one place:

var strings = new List<string> { "abc", "def" };
strings.Add("43");
IList<string> objects = (IList<string>)strings;
objects.Add("78");
var objectlist = (IList<object>)strings;
objectlist.Add(78);


We have created a list object "strings" which can take string values. The List has a method called Add() which helps in adding the new string values to the list.

Now let us try to create an instance of an interface IList<string> and cast the strings to the type IList<string> as the IList<string> is an interface which is implemented by a List<string> and now let us test if it still supports the method Add() .

Now try to cast the strings to an IList<object> and you will find that at compile time the compiler cannot stop you from typecasting. But when you run, you will see the error as in the following at the line of casting to IList<object>:

Unable to cast object of type "System.Collections.Generic.List`1[System.String]" to type "System.Collections.Generic.IList`1[System.Object]".

This is because you cannot cast a List<string> to an IList<object> as the later one can take any type not only the string value. Luckily C# gives you an error at runtime.

Now compare the casting of strings to an IList<object> and try to cast the object strings to an IEnumerable<object> which is valid and it will not break the code. Why does it not break the code? If you place your cursor on the IEnumerable and right-click and select "Go to Definition" you will find that it will not receive any input types and it only returns output (OUT KEYWORD).

var strings = new List<string> { "abc", "def" };
strings.Add(
"43");
IList<
string> objects = (IList<string>)strings;
objects.Add(
"78");
//var objectlist = (IList<object>)strings;
//objectlist.Add(78);
var objectseq = (IEnumerable<object>)strings;
#region Assembly mscorlib.dll, v4.0.30319
// C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0\Profile\Client\mscorlib.dll
#endregion 
using System.Collections;
using System.Runtime.CompilerServices; 
namespace System.Collections.Generic
{
    
//  Summary:
    //     Exposes the enumerator, which supports a simple iteration over a collection
    //     of a specified type.
    //
    //  Type parameters:
    //    T:
    //     The type of objects to enumerate.This type parameter is covariant. That is,
    //     you can use either the type you specified or any type that is more derived.
    //     For more information about covariance and contravariance, see Covariance
    //     and Contravariance in Generics.
    [TypeDependency("System.SZArrayHelper")]
    
public interface IEnumerable<out T> : IEnumerable
    {
        
//  Summary:
        //     Returns an enumerator that iterates through the collection.
        //
        //  Returns:
        //     A System.Collections.Generic.IEnumerator<T> that can be used to iterate through
        //     the collection.
        IEnumerator<T> GetEnumerator();
    }
}

Now let us check Contravariance by adding extra code as in the following.

Add a method as in the following:

static void MyAction(object o)
{
      Console.WriteLine(
"MY Action");
}

Now add the code in the method Main(), as in the following:

Action<object> actObject = MyAction;
Action<string> actString = actObject;

If you compile the program it works. You can place your mouse on the "Action" and right-click and select "go to definition" and you will see that the Action does not return anything, but receives a type as input.

#region Assembly mscorlib.dll, v4.0.30319
// C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0\Profile\Client\mscorlib.dll
#endregion
namespace
System
{
   
//  Summary:
    //     Encapsulates a method that has a single parameter and does not return a value.
    //
    //  Parameters:
    //    obj:
    //     The parameter of the method that this delegate encapsulates.
    //
    //  Type parameters:
    //    T:
    //     The type of the parameter of the method that this delegate encapsulates.This
    //     type parameter is contravariant. That is, you can use either the type you
    //     specified or any type that is less derived. For more information about covariance
    //     and contravariance, see Covariance and Contravariance in Generics.
    public delegate void Action<in T>(T obj);
}

You can find more details about this at the link (and I have taken examples from it): http://blogs.msdn.com/b/csharpfaq/archive/2010/02/16/covariance-and-contravariance-faq.aspx.

Here is the total code:

class Program
{
   
static void Main(string[] args)
    {
       
var strings = new List<string> { "abc", "def" };
        strings.Add(
"43");
        IList<
string> objects = (IList<string>)strings;
        objects.Add(
"78");
       
//var objectlist = (IList<object>)strings;
        //objectlist.Add(78);
        //Covariance
        // An object that is instantiated with a more derived type argument
        // is assigned to an object instantiated with a less derived type argument.
        // Assignment compatibility is preserved.
        var objectseq = (IEnumerable<object>)strings;
       
//Contravariance
        Action<object> actObject = MyAction;
       
// An object that is instantiated with a less derived type argument
        // is assigned to an object instantiated with a more derived type argument.
        // Assignment compatibility is reversed.
        Action<string> actString = actObject;
    }
   
static void MyAction(object o)
    {
        Console.WriteLine(
"MY Action");
    }
}

If you want to check if the preceding code works with earlier versions of .NET, you can try and check by executing the same code in Visual Studio 2008. You will get errors as the IEnumerable and Action signatures are different in earlier versions. I have attached the code from .NET 3.5 for your reference.

    // Summary:
    //     Exposes the enumerator, which supports a simple iteration over a collection
    //     of a specified type.
    //
    // Type parameters:
    //   T:
    //     The type of objects to enumerate.
    public interface IEnumerable<T> : IEnumerable
    {
       
// Summary:
        //     Returns an enumerator that iterates through the collection.
        //
        // Returns:
        //      A System.Collections.Generic.IEnumerator<T> that can be used to iterate through
        //      the collection.
        IEnumerator<T> GetEnumerator();
    }
    // Summary:
    //     Encapsulates a method that takes a single parameter and does not return a
    //     value.
    //
    // Parameters:
    //    obj:
    //     The parameter of the method that this delegate encapsulates.
    //
    // Type parameters:
    //    T:
    //     The type of the parameter of the method that this delegate        encapsulates.
    public delegate void Action<T>(T obj);

Notice that there is no "in" and "out" return types specified in their signatures.

Here is the essence of what Covariance and Contravariance are:

If a generics interface or delegate has a reference type T as its parameter and does not have a method or member that takes in a parameter of type T then we can declare it to be a covariant of type T. This ensures that type safety is preserved. If on the other hand there are no methods or members returning the type T then we can declare it as a Contravariant of type T. On the other hand C# arrays have been supporting covariance from the very beginning.

Dynamic

In .NET 4.0, the Dynamic Language Runtime is included by default. The DLR unifies multiple dynamic dispatches or late binding approaches.

Dynamic keywords can be used wherever a type can be specified and the variable of type Dynamic is resolved at runtime and not at compile time. So, dynamic types do not have an idea of what the methods are that a particular value supports until the program is run. So, it would be the responsibility of the programmer to call the right methods with the dynamic types.

Check the following program on how to use dynamic types:

Dynamic-Language.gif

Not only that you can create a variable without assigning any value. Also dynamic types can be used as a parameter of a method. But, you cannot do that using a VAR keyword introduced in .NET 3.0.

VAR-keyword-introduced.gif

But it is possible with dynamic types.

dynamic-types.gif

Note: But, dynamic types cannot replace the existing data types and they will be a little bit slower while compiling as the compiler has to generate a lot of code to compile the dynamic types when compared to the .NET existing data types.

New Compiler Options

The New Compiler Options are:

  1. COM Interoperability
  2. Specifying an app.config file to the C# compiler

In the previous versions of .NET, when we wanted to use any of the COM objects we had to declare all our variables as objects and pass them with the ref keyword. Second, we needed to pass Missing.Value even if we are not using the parameter and the third one was that we ended up with huge interop assemblies.

With the new feature of C# 4.0 like optional parameters and, dynamic, the coding for COM interoperability has become significantly easier. In 4.0, we do not need to pass optional parameters and there are not huge numbers of assemblies.

The following code has been taken from the Wikipedia as there is some issue with the interop DLLs in my computer now. The following code is for saving a document file. If you see we have to write many missing values and many optional parameters in the previous versions.

interop-DLLs.gif

Now in C# 4.0 the preceding code can be simply modified as in the following:

interop-DLLs1.gif

The /appconfig option enables a C# application to specify the location of an assembly's application configuration file to CLR at assembly binding time.

This option helps you to specify if your assembly has to refer to multiple versions of a particular assembly at the same time. In order to do that we need to disable the default behavior by using the <supportPortability> tag.

The following example is taken from MSDN and you can refer to the article at http://msdn.microsoft.com/en-us/library/ee523958(v=vs.100).aspx. This example shows an app.config file that enables an application to have references to both the .NET Framework implementation and the .NET Framework for Silverlight implementation of any .NET Framework assembly that exists in both implementations. The /appconfig compiler option specifies the location of this app.config file.

<configuration>
    <runtime>
        <assemblyBinding>
            <supportPortability PKT="7cec85d7bea7798e" enable="false"/>
            <supportPortability PKT="31bf3856ad364e35" enable="false"/>         
    </assemblyBinding>     
  </runtime>
</configuration>