Cannot pass a List to a method expecting a Li

2020-03-19 03:36发布

问题:

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)

回答1:

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.



回答2:

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.



回答3:

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.



回答4:

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



回答5:

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.