Невозможно передать List <Foo>методу, ожидающему List <IFoo>, где Foo: IFoo - PullRequest
7 голосов
/ 01 августа 2011

У меня есть класс Foo, реализующий интерфейс IFoo.У меня есть метод, принимающий List<IFoo> в качестве параметра.Однако он не может конвертировать из List<Foo> в List<IFoo> - это меня удивляет, так как Foo реализует интерфейс IFoo.

Как я могу обойти это и почему это происходит?(Всегда хорошо учиться на ошибках)

Ответы [ 5 ]

12 голосов
/ 01 августа 2011

Это потому, что List<T> не является ковариантным. Подробнее см. Ковариация и Контравариантность в C # .

Если вместо этого вы можете заставить свой метод работать на IEnumerable<T>, это сработает (вы можете передать List<Foo> в IEnumerable<IFoo> в .NET 4, поскольку он определен IEnumerable<out T>).

Причина List<T> не ковариантна, кстати, в том, что она не определяет контракт только для чтения. Так как List<T> явно позволяет добавлять элементы (через Add(T)), разрешено работать ковариантности. Если бы это было разрешено, метод ожидал, что сможет добавить в список элемент типа Bar (если Bar происходит от IFoo), но это не получится, поскольку список действительно а List<Foo>, а не List<IFoo>. Так как IEnumerable<T> позволяет вам только перебирать список, но не изменять его, он может быть ковариантным и просто работать в соответствии с ожиданиями.

8 голосов
/ 01 августа 2011

Хотите верьте, хотите нет, это небезопасно.

Рассмотрим следующий код:

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

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

Вы запрашиваете ковариантное преобразование; это возможно только для неизменяемых типов.
Кроме того, .Net поддерживает ковариацию только для интерфейсов.

Измените метод на IEnumerable<IFoo>, и он будет работать.

2 голосов
/ 01 августа 2011

Помните, что НИКАКИХ наследований или реализационных отношений нет между специализациями универсального типа. List<Foo> никак не наследуется от List<IFoo>. Это похоже на сравнение string с int.

Это не значит, что между типами вообще нет никакой связи; Просто эти отношения не являются наследством. Вместо этого у нас есть отношения специализация между двумя типами. Некоторые типы в .Net 4.0 и более поздних версиях поддерживают атрибут специализации, известный как ковариация . Совместная и противоречивая позволяют специализированной части универсального типа изменять (обратите внимание на коренное слово там) при определенных обстоятельствах List<T> не относится к таким типам.

Но то, что я бы сделал в этом случае (где я точно не знаю, что у вас есть .Net 4.0), это сделал весь метод обобщенным:

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

Обратите внимание, что я также использовал IEnumerable<T> вместо List<T>. Вы все еще должны иметь возможность передавать списки этой функции, потому что между IEnumerable<T> и List<T> существует отношение наследования / реализации. Вы также можете обрабатывать массивы и любые другие поддерживаемые последовательности. IEnumerable также является одним из типов, поддерживающих ковариацию. Почти любой метод, который требует аргумент List<T>, должен быть обновлен, чтобы использовать вместо него IEnumerable<T>.

0 голосов
/ 01 августа 2011

Ну, вы можете преобразовать его в новый список, объявленный как List<IFoo>, что вам и нужно:

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

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

Это будет работать, я думаю ... потому что Foo можно преобразовать в IFooToList возвращает новый список с указанием типа.

0 голосов
/ 01 августа 2011

Вам нужно будет создать новый List<IFoo> и добавить в него все элементы из первого списка.

...