codethinked (kōdthĭngked) adj. To be consumed by or obsessed with code.

C# 4.0 New Features Part 4 - Generic Contravariance

Here are the previous parts to this series:

Part 1 - Dynamic Keyword

Part 1.1 - Dynamic Keyword Second Look

Part 2 - Default And Named Parameters

Part 2.1 Default Parameter Intrigue

Part 3 Generic Covariance

I went on a serious blogging streak, and then all the sudden I just dropped off with my C# 4.0 new features series. I guess that just goes to show you that I need to spread this stuff out! Anyways, I am back today with part 4 of this series on generic contravariance. This post is actually probably going to be pretty short, because contravariance is a topic that is very similar to covariance, but it also isn't really all that interesting. It can be quite useful though, and that is why I am bringing it to you here.

In the last post we created an interface that looks like this:

public interface IContainer<out T>  
{  
    T GetItem();  
}

With this interface we can now say that IContainer is covariant on T, which as explained previously, means that this type can now return objects of Type T and anything more specific than T (subclasses). But what does it mean for something to be contravariant? Well, it is probably easier to show it than it is to explain it. First we can take a look at our class which implements the code above:

public class Container<T> : IContainer<T>
{
    private T item;

    public Container(T item)
    {
        this.item = item;
    }

    public T GetItem()
    {
        return item;
    }    
}

We have our container class above, and we can pass in our item in the constructor and use it like this:

Circle circle = new Circle();
IContainer<Shape> container = new Container<Circle>(circle);

So you see that we are declaring a class of type Circle and then passing that into our Container class which is being assigned to the IContainer<Shape>. Here we are seeing Covariance in action. But what happens if we want to perform some action on the item that our container is holding? We could add a method like this to our interface:

public interface IContainer<out T>
{
    T GetItem();
    void Do(Action<T> action);
}

This won't work though. Well, why not? The issue is that Action is not contravariant on T (it will be in the .net 4.0 release, but it is not yet). Since Action is not contravariant on T, if we declare IContainer<Shape> then the Action delegate would need to be able to accept type T and anything more specific than it.

In order to do this, we first need to declare a new Action delegate type:

public delegate void ContraAction<in T>(T a);

So you see that our keyword for contravariance is "in", since these types can only be passed in to a method. Now we can define our interface like this:

public interface IContainer<out T>
{
    T GetItem();
    void Do(ContraAction<T> action);
}

And so if we declare our container class like this:

public class Container<T> : IContainer<T>
{
    private T item;

    public Container(T item)
    {
        this.item = item;
    }

    public T GetItem()
    {
        return item;
    }

    public void Do(ContraAction<T> action)
    {
        action(item);
    }
}

We can then use it like this:

Circle circle = new Circle();
IContainer<Shape> container = new Container<Circle>(circle);
container.Do(s => s.MethodCallOnShape());

Pretty sweet, huh? So we have a container that can hold a Circle, Square, Triangle, etc... which can then take a delegate into a method that will perform an action on the base "Shape" type, but will accept any shape as a parameter.

Comments

pingback

Pingback from blog.cwa.me.uk

Reflective Perspective - Chris Alcock  » The Morning Brew #221

blog.cwa.me.uk

November 12. 2008 00:26

pingback

Pingback from alvinashcraft.com

Dew Drop - November 12, 2008 | Alvin Ashcraft's Morning Dew

alvinashcraft.com

November 12. 2008 05:56

trackback

Trackback from DotNetKicks.com

C# 4.0 New Features Part 4 - Generic Contravariance

DotNetKicks.com

November 14. 2008 10:03

Scott Parker

Just a quick note - the "Part 3" link in the article points to the clarification on default method values. The "related links" has it however.

Thanks for the thoughtful article, and please keep up the good work.
-Scott

Scott Parker

November 20. 2008 12:58

United States
Adam

The link in your article to C# 4.0 Features Part 3 is linking to Part 2.1 Smile

Adam

November 21. 2008 20:06

United States
Justin Etheredge

@Scott and @Adam Thanks, it is fixed now.

Justin Etheredge

November 22. 2008 18:10

United States
Dmitriy Nagirnyak

I don't buy what the difference is between normal generics?
And why can't we just use something like:
public interface IContainer<T>
{    
    T GetItem();    
}

Maybe I just should read with more attention...

Cheers.

Dmitriy Nagirnyak

December 16. 2008 01:56

Australia
Justin Etheredge

Generics in C# 2.0 and 3.0 are not variant. Meaning you cannot assign a List<Dog> to a List<Animal> and vice versa. Intuitively this should work, but in practice there are several reasons why this doesn't work well with strong typing. In C# 4.0 generics will have the ability to do something similar to what I stated above, but using interfaces.

Justin Etheredge

December 16. 2008 08:25

United States
pingback

Pingback from bogdanbrinzarea.wordpress.com

Summaries 11.04.2009 – 13.04.2009 « Bogdan Brinzarea’s blog

bogdanbrinzarea.wordpress.com

April 14. 2009 02:26

pingback

Pingback from bogdanbrinzarea.wordpress.com

Learning .NET 4.0 new features «  Bogdan Brinzarea’s blog

bogdanbrinzarea.wordpress.com

April 23. 2009 23:16

Add Comment


(Will show your Gravatar icon)

  Country flag

biuquote
  • Comment
  • Preview
Loading