Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Cannot pass a List<Foo> to a method expecting a List<IFoo>, where Foo : IFoo

I have a class Foo implementing the IFoo interface. I have a method taking a List<IFoo> as a parameter. However, it cannot convert from List<Foo> to List<IFoo> - this surprises me, since Foo implements the IFoo interface.

How can I get around this, and why does this occur? (Always good to learn from mistakes)

like image 928
The Communist Duck Avatar asked Aug 01 '11 17:08

The Communist Duck


3 Answers

This is because List<T> is not covariant. For details, see Covariance and Contravariance in C#.

If you can make your method work on IEnumerable<T> instead, this will work (you can pass List<Foo> into IEnumerable<IFoo> in .NET 4, since it's defined IEnumerable<out T>).

The reason List<T> is not covariant, btw, is because it's not defining a read only contract. Since List<T> explicitly allows you to add elements (via Add(T)), it's not safe to allow covariance to work. If this was allowed, the method would expect to be able to add an element of type Bar (if Bar derives from IFoo) to the list, but that would fail, since the list is really a List<Foo>, not a List<IFoo>. Since IEnumerable<T> only allows you to iterate through the list, but not modify it, it can be covariant and just work as expected in this case.

like image 66
Reed Copsey Avatar answered Oct 31 '22 05:10

Reed Copsey


Believe it or not, this is not typesafe.

Consider the following code:

List<Foo> fooList = new List<Foo>();
List<IFoo> iFooList = fooList;    //Illegal

iFooList.Add(new SomeOtherFoo()); //That's not Foo!

You're asking for a covariant conversion; that's only possible for immutable types.
In addition, .Net only supports covariance for interfaces.

Change the method to take an IEnumerable<IFoo> and it will work.

like image 23
SLaks Avatar answered Oct 31 '22 03:10

SLaks


Remember that there is NO inheritence or implementation relationship between specializations of a generic type. List<Foo> does not inherit from List<IFoo> in any way. It's like comparing string to int.

This is not to say there is no relationship between the types at all; just that the relationship in question is not inheritance. Instead, we have a specialization relationship between the two types. Certain types in .Net 4.0 and later support an attribute of specialization known as covariance. Co- and contra- variance allow the specialized portion of a generic type to vary (note the root word there) under certain circumstances. List<T> is not one of those types.

But what I would do in this case (where I don't know for sure you have .Net 4.0) is make the whole method generic:

public void MyMethod<T>(IEnumerable<T> items) where T : IFoo
{
    //...
}

Note that I also used IEnumerable<T> rather than List<T>. You should still be able to pass lists to this function, because there is an inheritance/implementation relationship between IEnumerable<T> and List<T>. You can also process arrays and any other supported sequence. IEnumerable is also one of the types that supports covariance. Just about any method that requires a List<T> argument should be updated to use IEnumerable<T> instead.

like image 2
Joel Coehoorn Avatar answered Oct 31 '22 03:10

Joel Coehoorn