hits counter

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);
    }
}

10 Comments

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

  • I almost died when I realised with .NET 2 this scenario:
    1. Base class, say Vehicle
    2. Derived class, say Car
    IEnumerable&lt;Car&gt; y;
    IEnumerable&lt;Vehicle&gt; x = y; // WILL NOT WORK! OUCH
    &nbsp;
    Obviously .NET 4 fixed this.
    I think 99% of people are confused by this feature (even reading your above post - sorry...). &nbsp;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. &nbsp;
    ;-)
    Regards,
    David

  • Thanks, Keith. I've now corrected it.

  • @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.

  • 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();


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

    ... and my ManagerFactory is:

    public class ManagerFactory : IManagerFactory 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();

    if paymentId=2 then var managerFactory = new ManagerFactory();
    etc.

    Many thanks if you can help me on this.

    Steve

  • 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&lt;CheckMoneyOrderPaymentManager&gt;();


    the type of the variable is ManagerFactory&lt;CheckMoneyOrderPaymentManager&gt;.


    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&lt;out T&gt;&nbsp;&nbsp;&nbsp;&nbsp; where T : new()
    {
    T GetPaymentManager(PaymentManagerTypes paymentManagerTypes);
    }



    Then you declare your ManagerFactory class as:


    public class IManagerFactory : IManagerFactory&lt;out T&gt;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; where T : new()
    {
    public T GetPaymentManager(PaymentManagerTypes paymentManagerTypes)
    {
    return new T();
    }
    }



    and use it like this:


    IManagerFactory&lt;PaymentManagerBase&gt; managerFactory;
    if (paymentId == 1)
    {
    &nbsp;&nbsp;&nbsp; managerFactory = new ManagerFactory&lt;CheckMoneyOrderPaymentManager&gt;();
    }
    else
    {
    &nbsp;&nbsp;&nbsp; managerFactory = new ManagerFactory&lt;ACHPaymentManager&gt;();
    }




    See if it works out for you:

  • 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

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

  • 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'.

  • 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.

Comments have been disabled for this content.