Paulo Morgado

.NET Development & Architecture

Recent Articles

view all

Events

Projects

Recent Readers

Visitor Locations

Visitor Locations

Disclaimer

The opinions and viewpoints expressed in this site are mine and do not necessarily reflect those of Microsoft, my employer or any community that I belong to. Any code or opinions are offered as is. Products or services mentioned are purchased by me, made available to me by my employer or the manufacturer/vendor which doesn't influence my opinion in any way.

C# 4.0: Covariance And Contravariance In Generics Made Easy

In my last post, I went through what is variance in .NET 4.0 and C# 4.0 in a rather theoretical way.

Now, I’m going to try to make it a bit more down to earth.

Given:

class Base { }

class Derived : Base { }

Such that:

Trace.Assert(typeof(Base).IsClass && typeof(Derived).IsClass && typeof(Base).IsGreaterOrEqualTo(typeof(Derived)));
  • Covariance

    interface ICovariantIn<out T> { }

    Trace.Assert(typeof(ICovariantIn<Base>).IsGreaterOrEqualTo(typeof(ICovariantIn<Derived>)));

  • Contravariance

    interface IContravariantIn<in T> { }

    Trace.Assert(typeof(IContravariantIn<Derived>).IsGreaterOrEqualTo(typeof(IContravariantIn<Base>)));

  • Invariance

    interface IInvariantIn<T> { }

    Trace.Assert(!typeof(IInvariantIn<Base>).IsGreaterOrEqualTo(typeof(IInvariantIn<Derived>))
        && !typeof(IInvariantIn<Derived>).IsGreaterOrEqualTo(typeof(IInvariantIn<Base>)));

Where:

public static class TypeExtensions
{
    public static bool IsGreaterOrEqualTo(this Type self, Type other)
    {
        return self.IsAssignableFrom(other);
    }
}

Comments

Twitter Trackbacks for C# 4.0: Covariance And Contravariance In Generics Made Easy - Paulo Morgado [asp.net] on Topsy.com said:

Pingback from  Twitter Trackbacks for                 C# 4.0: Covariance And Contravariance In Generics Made Easy - Paulo Morgado         [asp.net]        on Topsy.com

# April 14, 2010 7:58 PM

Keith said:

you have a typo, you don't have the right definition for  your IContravariantIn

# April 15, 2010 12:46 AM

David Taylor said:

I almost died when I realised with .NET 2 this scenario:

1. Base class, say Vehicle

2. Derived class, say Car

IEnumerable<Car> y;

IEnumerable<Vehicle> x = y; // WILL NOT WORK! OUCH

 

Obviously .NET 4 fixed this.

I think 99% of people are confused by this feature (even reading your above post - sorry...).  The best way for people to get their head around this is just to give a couple of simple code example (very simple ones) of things that would not work before .NET 4, and will work with .NET 4.  

;-)

Regards,

David

# April 15, 2010 1:12 AM

Paulo Morgado said:

Thanks, Keith. I've now corrected it.

# April 15, 2010 3:26 AM

Paulo Morgado said:

@David,

Bether late than never. :)

Well, Anders, himself, says that C# 2.0 was all about what they didn't have time to put in C# 1.0 because shipping is a feature.

# April 15, 2010 3:31 AM

SteveMets said:

Hi Paul,

I have been trying for a long time to use the same anonymous variable, and have it automatically cast into the correct type (given that all the types implement the same interface).  I thought that Covariance was the answer, but I am still not getting it. e.g.

var managerFactory = new ManagerFactory<CheckMoneyOrderPaymentManager>();

var vCheckMoneyOrderPayment =managerFactory.GetPaymentManager(Extended.GetPaymentManagerTypes(paymentTypeId));

... and my ManagerFactory is:

  public class ManagerFactory<T> : IManagerFactory<T> where T : new()

   {

public T GetPaymentManager(Extended.PaymentManagerTypes paymentManagerTypes)

       {

           return new T();  

       }

   }

How can I get the correct behavior, so that

if paymentId=1 then var managerFactory = new ManagerFactory<CheckMoneyOrderPaymentManager>();

if paymentId=2 then var managerFactory = new ManagerFactory<ACHPaymentManager>();

etc.

Many thanks if you can help me on this.

Steve

# August 23, 2011 12:08 PM

Paulo Morgado said:

Hi Steve,

I see you are a bit confused.

There are no anonymous variables because the only way in the code to reference a variable is by its name.

What you are referring to is implicitly typed local variable declarations (§8.5.1).

In an implicitly typed local variable declaration, the type of the local variable being declared is taken to be the same as the type of the expression used to initialize the variable.

In this code:

var managerFactory = new ManagerFactory<CheckMoneyOrderPaymentManager>();

the type of the variable is ManagerFactory<CheckMoneyOrderPaymentManager>.

I suppose that your CheckMoneyOrderPaymentManager and ACHPaymentManager both have a common base type (say PaymentManagerBase).

Given that, you need to define your interface as:

public interface IManagerFactory<out T>     where T : new() { T GetPaymentManager(PaymentManagerTypes paymentManagerTypes); }

Then you declare your ManagerFactory class as:

public class IManagerFactory : IManagerFactory<out T>      where T : new() { public T GetPaymentManager(PaymentManagerTypes paymentManagerTypes) { return new T(); } }

and use it like this:

IManagerFactory<PaymentManagerBase> managerFactory;

if (paymentId == 1)
{
    managerFactory = new ManagerFactory<CheckMoneyOrderPaymentManager>();
}
else
{
    managerFactory = new ManagerFactory<ACHPaymentManager>();
}

See if it works out for you:

# August 23, 2011 7:09 PM

SteveMets said:

Paul,

Thanks for the feedback.  I realized that I wasn't going to be able to pass in some sort of T since my value was an integer, and had to re-work it pretty much like you have it.  Your code may be pushing it a little more generic than my efforts, but it still requires a block of if statements or a switch statement to choose which implicit type you want to create.  The problem I encountered was that covariance and contravariance will can not take the System.Types as input, which is the value in the if block.  Anyway, this is a very good treatment for the topic, and I will use it when appropriate.

Thanks

# August 26, 2011 10:41 AM

SteveMets said:

Also, I think you meant to name the ManagerFactory class 'ManagerFactory, not 'IManagerFactory'- but I like the implementation!

# August 26, 2011 10:45 AM

Paulo Morgado said:

Steve,

I don't understand what you mean by "covariance and contravariance will can not take the System.Types as input".

Covariance and contravariance aplies only to interfaces. That's why I used 'IManagerFactory'.

# August 26, 2011 8:11 PM

SteveMets said:

Yes, exactly.  The type that I wanted to pass in was an integer- It could not work.  Even when you have the interface designed as above (which is almost exactly what I ended up doing), you still have to pass the type you want to the interface- you have to know what you want to create.  I was hoping to do it very generically, but there still has to be a decision tree (if blocks or switch) to make a choice.  Also, my manager classes do not (yet) inherit from a base interface, as I'm using a repository to do that.  I could in the future add another IManagerBase that they could all inherit from, but right now they are just using my IRepository for dependency injection.

Thanks- I look forward to future articles.

# August 29, 2011 2:40 PM
Leave a Comment

(required) 

(required) 

(optional)

(required)