Почему я не могу вернуть List <Foo>, если вас попросил List <IFoo>?

Я понимаю, что если S является дочерним классом T, то a List<S> является not дочерним элементом List<T>. Хорошо. Но интерфейсы имеют другую парадигму: если Foo реализует IFoo, то почему List<Foo> нет (пример) a List<IFoo>?

Как не может быть никакого фактического класса IFoo, означает ли это, что мне всегда приходилось бросать каждый элемент списка при экспонировании List<IFoo>? Или это просто плохой дизайн, и я должен определить свой собственный класс коллекции ListOfIFoos, чтобы иметь возможность работать с ними? Мне тоже не кажется разумным...

Каким будет лучший способ разоблачения такого списка, учитывая, что я пытаюсь программировать интерфейсы? В настоящее время я склонен к фактическому хранению моего List<Foo> внутри .

Ответ 1

Ваш List<Foo> не является подклассом, если List<IFoo>, потому что вы не можете сохранить в нем объект MyOwnFoo, который также является реализацией IFoo. (Принцип подстановки Лискова)

Идея сохранения List<IFoo> вместо выделенного List<Foo> в порядке. Если вам нужно лить содержимое списка в его тип реализации, это, вероятно, означает, что ваш интерфейс не подходит.

Ответ 2

Вот пример того, почему вы не можете этого сделать:

// Suppose we could do this...
public List<IDisposable> GetDisposables()
{
    return new List<MemoryStream>();
}

// Then we could do this
List<IDisposable> disposables = GetDisposables();
disposables.Add(new Form());

В этот момент список, созданный для хранения MemoryStreams, теперь имеет форму. Плохо!

Таким образом, это ограничение существует для поддержания безопасности типов. В С# 4 и .NET 4.0 будет ограниченная поддержка этого (он называется дисперсией), но он по-прежнему не поддержит этот конкретный сценарий, в точности по указанным выше причинам.

Ответ 3

В вашей возвращаемой функции вам нужно сделать список списком интерфейсов, а когда вы создадите объект, сделайте его как объект, который его реализует. Вот так:

function List<IFoo> getList()
{
  List<IFoo> r = new List<IFoo>();
  for(int i=0;i<100;i++)
      r.Add(new Foo(i+15));

  return r;
}

Ответ 4

МАССИВНОЕ ИЗОБРАЖЕНИЕ Вы сможете сделать это с С# 4.0, но [спасибо Jon]

Вы можете обойти это, используя ConvertAll:

public List<IFoo> IFoos()
{
    var x = new List<Foo>(); //Foo implements IFoo
    /* .. */
    return x.ConvertAll<IFoo>(f => f); //thanks Marc
}

Ответ 5

Простым ответом является то, что List<Foo> является другим типом List<IFoo>, таким же образом, что DateTime отличается от IPAddress, например.

Однако тот факт, что у вас есть IFoo, предполагает, что коллекции IFoo должны содержать как минимум две реализации IFoo (FooA, FooB и т.д.), потому что, если вы ожидаете существует только одна реализация IFoo, Foo, тогда тип IFoo является избыточным.

Итак, если только когда-либо будет один производный тип интерфейса, забудьте интерфейс и сохраните накладные расходы. Если есть два или более производных типа интерфейса, то всегда используйте тип интерфейса в коллекциях/общих параметрах.

Если вы обнаружите, что пишете код thunking, значит, где-то есть ошибка дизайна.

Ответ 6

Если в то время, когда был изобретен IList<T>, Microsft знал, что будущие версии .net будут поддерживать ковариацию интерфейса и контравариантность, было бы возможно и полезно разделить интерфейс на IReadableList<out T>, IAppendable<in T> и IList<T>, которые наследуют оба вышеперечисленных. Это наложило бы небольшую дополнительную работу на разработчиков vb.net(им пришлось бы определять как версии для чтения, так и чтения-записи индексированного свойства, поскольку по какой-то причине .net не разрешает чтение-запись свойство, которое должно выполняться как свойство только для чтения), но будет означать, что методы, которые просто должны читать элементы из списка, могут получать IReadableList<T> ковариантным образом, а методы, которым просто нужна коллекция, которую они могут добавить, могут получить IAppendable<T> контравариантным образом.

К сожалению, единственное, что может быть реализовано сегодня, было бы, если бы Microsoft предоставила средства для новых интерфейсов, которые можно было бы заменить на старые, с использованием старых интерфейсов, автоматически используя методы по умолчанию, предоставляемые новыми. Я бы подумал, что такая функция (взаимозаменяемость интерфейса) будет чрезвычайно полезна, но я бы не задерживал дыхание, ожидая, что Microsoft ее выполнит.

Учитывая, что для IReadableList<T> в IList<T> не существует возможности альтернативного подхода, альтернативным подходом было бы определение одного собственного интерфейса, связанного с списком. Одна из трудностей при этом состоит в том, что все экземпляры System.Collections.Generic.List<T> должны быть заменены каким-либо другим типом, хотя сложность этого может быть сведена к минимуму, если нужно определить структуру List<T> в другом пространстве имен, содержащем одиночное поле System.Collections.Generic.List<T> и определение расширяющихся преобразований к типу системы и из него (с использованием структуры, а не класса, означало бы, что код будет избегать необходимости создания новых объектов кучи при кастинге в любом сценарии, где структура не должна быть в штучной упаковке).