0

I'm working with C#5/.NET 4.5 in Visual Studio 2013 Pro on Windows 7.

I'd like to work with collections of items where the only requirement is that the items implement a specific interface. The items in the collection will not necessarily be part of a common inheritance tree (i.e. the collection will not be based on a common ancestor), they could be arbitrary classes but will all implement the specified interface (so the goal is to base the collection on the common interface that they all implement).

I'm having trouble with this. If I have a collection of items which implement the desired interface, I'm unable to pass that collection to a method which requires a collection based on the interface.

Here's some code to make this more concrete.

using System.Collections.Generic;

class Test
{
    public interface IDrawable
    {
        void Draw();
    }

    public class Shape : IDrawable
    {
        public void Draw() {}
    }

    public void DrawItems(List<IDrawable> itemsToDraw) {}

    public Test()
    {
        List<Shape> shapes = new List<Shape>();
        DrawItems(shapes);
    }
}

The call to DrawItems generates a compiler error. The method is expecting a collection of items implementing IDrawable. I'm passing in a collection of Shape objects. Since the Shape objects do implement IDrawable, I was hoping I could get away with this.

Looks like this will not work, but I'm trying to understand why. I looked up info on covariance and contravariance, but didn't find anything specifically addressing this case.

Any ideas or info on why this doesn't work?

Nerdtron
  • 1,486
  • 19
  • 32

1 Answers1

7

This is an extremely frequently asked question.

As you note, the feature you want is called covariance; a search for that on this site should show you plenty of similar questions.

Lists are not covariant because they cannot be made covariant safely. You have a bowl of apples and you want to use it as a bowl of fruit, but you can put a banana into a bowl of fruit, but you cannot put a banana into a bowl of apples. Therefore you may not use a bowl of apples as a bowl of fruit. Same thing here. A list of shapes cannot be used as a list of drawable things, because you can put a non-shape into the list of drawable things.

IEnumerable<T> is covariant in C# 4 and higher if T is a reference type. You should be passing around sequences instead of lists if you need covariance. A List<Shape> is convertible to IEnumerable<IDrawable> because there's no way to use IEnumerable<IDrawable> to add a non-shape to the list.

If you want more information about covariance and contravariance in C#, consult my MSDN blog:

http://blogs.msdn.com/b/ericlippert/archive/tags/covariance+and+contravariance/

Start at the bottom; these are in reverse-chronological order.

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • Thanks! I searched and found similar explanations, but it wasn't clear to me how the issue with adding incompatible items translated to being unable to pass around a collection. In reading your reply, I guess I was assuming read-only characteristics which weren't actually there. But that makes sense now. Thanks for the link as well, I'll go check that out. – Nerdtron Aug 23 '14 at 05:39
  • 1
    @Nerdtron: You're welcome! This was my favourite feature of C# 4. – Eric Lippert Aug 23 '14 at 05:40