7

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)

PrettyPrincessKitty FS
  • 6,117
  • 5
  • 36
  • 51

5 Answers5

12

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.

Reed Copsey
  • 554,122
  • 78
  • 1,158
  • 1,373
  • This was very useful; thanks! I replaced the parameter definition(?) with `IEnumerable` and the errors went; however, is there any chance of hidden bugs by doing this? I'd prefer to reach a solid solution than just hiding errors..(I am just iterating the list and calling method `Bar`) – PrettyPrincessKitty FS Aug 01 '11 at 17:41
  • 2
    @The Communist Duck: Nope. That's why `IEnumerable` has the "out" in the definition. It's perfectly safe to use an IEnumerable with covariance, as you're doing here. As long as you're iterating, this works perfectly (which is why the language now supports it!) – Reed Copsey Aug 01 '11 at 17:43
8

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.

SLaks
  • 868,454
  • 176
  • 1,908
  • 1,964
2

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.

Joel Coehoorn
  • 399,467
  • 113
  • 570
  • 794
  • If you're using .NET 4, this doesn't need to be generic. Just using `public void MyMethod(IEnumerable items)` will work due to the fact that `IEnumerable` is already a covariant interface. This is an option for in .NET 2-3.5, though. – Reed Copsey Aug 01 '11 at 18:03
0

You will need to create a new List<IFoo> and add all the items from the first list to it.

Dark Falcon
  • 43,592
  • 5
  • 83
  • 98
0

Well, you can convert it to new list declared as List<IFoo> which is what you need like so:

List<Foo> list = new List<Foo>();

theMethodThatTakesIFooList(list.ToList<IFoo>());

This will work I guess... because Foo is castable to IFoo, and ToList returns new list of type specefied.

Cipi
  • 11,055
  • 9
  • 47
  • 60