Covariance and Contravariance in C# 4.0

November 30, 2009 Leave a comment

In .NET Framework 4, C#( or Visual Basic) support covariance and contravariance in generic interface and delegates and allow for implicit conversion of generic type parameters.

Covariant and contravariant generic type parameters provide greater flexibility in assigning and using generic types.Covariance is the ability to use a more derived type than that originally specified. Contravariance is the ability to use a less derived type.

 

Covariance

Covariant type parameters enable you to make assignments that look much like ordinary polymorphism. Suppose you have a base class and a derived class, named Base and Derived. Polymorphism enables you to assign an instance of Derived to a variable of type Base.

Let take the example in C# 3.0.

 

class Base
{
        public string Name { get; set; }
        public static void PrintBases(IEnumerable<Base> bases)
        {
            foreach (Base b in bases)
            {
                Console.WriteLine(b.Name);
            }
        }
    }

    class Program : Base
    {
        public static void Main()
        {
            List<Program> derivedList= new List<Program>()
            {
                new Program(){
                    Name="a"
                },
                new Program(){
                    Name="b"
                }
            };

            Program.PrintBases(derivedList); 
            IEnumerable<Base> baseList = derivedList;  // Allow in c# 4.0
            Console.ReadLine();
        }
    }

The following doesnt work  in C# 3.0. but its work on C# 4.0

This is because the type parameter of the IEnumerable <T> interface is covariant, you can assign an instance of IEnumerable<Program> to a variable of type IEnumerable<Base>. Here In example The List < T> class implements the IEnumerable < T> interface, so List<Program> implements IEnumerable<Program>.  and The covariant type parameter does the rest.

How it work ?

Let take a look of the declaration of  IEnumerable<T> interface.

public interface IEnumerable<out T>

{

          IEnumerator<T>  GetEnumerator();

}

public interface IEnumerator<out t>

{

        T Currrent {get;}

        bool MoveNext();

}

In C# 4.0 it is possible to indicate co-variance and contra-variance by placing either an out or an in modifier on the type argument. Here out means co-variance and T is treated as co-variantly  and T is restricted to output positions only. Here in the interface T is only used as output positions i.e  it can only used as the result of the method call or a result of the property access . it can not be a parameter.  so it is possible to assign IEnumerable<derived> to IEnumerable<base> because  the compiler know this this is safe. there is nothing here that go wrong. because it is only use as output positions not as input positions.

 

Contravariance

The contravariant type parameters are used only for the parameter types of its members.

Look at the following Code

 

class Account
{
    public long AccountID { get; set; }
    public long CustomerID { get; set; }
}

class CreditCardAccount : Account
{
    public DateTime ValidTill { get; set; }
    public DateTime ValidFrom { get; set; }
}

class CustomerIdentityComparer : IEqualityComparer<Account>
{
    bool IEqualityComparer<Account>.Equals(Account a, Account b)
    { 
    return a == b ? true : a == null || b == null ? false : a.CustomerID == b.CustomerID;
    }
    int IEqualityComparer<Account>.GetHashCode(Account a)
    {
        return a == null ? 0 : a.CustomerID.GetHashCode();
    }
}

class Program
{
    static void Main()
    {
        List<CreditCardAccount> cc = new List<CreditCardAccount> {
            new CreditCardAccount() {
                AccountID = 1,
                CustomerID=12,
                ValidFrom=new DateTime(2009,1,10),
                ValidTill=new DateTime(2011,1,1)
            } ,
            new CreditCardAccount() {
                AccountID = 1,
                CustomerID=12,
                ValidFrom=new DateTime(2009,1,10),
                ValidTill=new DateTime(2011,1,1)
            } ,
            new CreditCardAccount() {
                AccountID =2,
                CustomerID=13,
                ValidFrom=new DateTime(2009,1,10),
                ValidTill=new DateTime(2011,1,1)
            } ,
            new CreditCardAccount() {
                AccountID = 3,
                CustomerID=14,
                ValidFrom=new DateTime(2009,1,10),
                ValidTill=new DateTime(2011,1,1)
            }
        };

// this is possible because the IEqualityComparer is Contravariance

        var kk = cc.Distinct(new CustomerIdentityComparer());

        foreach (var c in kk)
        {
            var _c = (CreditCardAccount)c; 
       Console.WriteLine("Account ID : {0} Valid From : {1}",c.AccountID,_c.ValidFrom);
        }
        Console.ReadLine();
    }
}

 

The example defines an Account class that has two properties: an account identification number and a customer identification number. CreditCardAccount inherits Account. Assuming that a customer can have multiple accounts, it would be useful to be able to get a list of the distinct customers. To do this, the example defines a CustomerIdentityComparer class that implements IEqualityComparer<Account> . The type parameter of the IEqualityComparer < T> generic interface is contravariant. This enables you to pass a comparer of a less derived type ( Account) when the code calls for a comparer of a more derived type ( CreditCardAccount). When the example calls the Distinct < TSource > extension method on the list of CreditCardAccount objects, each CreditCardAccount is passed to the Equals method of the IEqualityComparer<Account> method. The parameter type of the method is less derived than the type that is being passed, so the call is type safe.

How it work ?

Let take a look of the declaration of  IEqualityComparer<T> interface

public interface IEqualityComparer<in T>

{

    bool Equals( T x, T y );

           int GetHashCode( T obj )

}

 

Here in means contra-variance and T is treated as contra-variantly  and T is restricted  to input positions only. i.e T can only be used as method parameter or write only property.

and lastly value-type are always invarient i.e IEnumarable<int> not IEnumerable<object>.

 

Shibu Bhattarai

Categories: C# Tags: , , ,
Follow

Get every new post delivered to your Inbox.