2008-11-25 4 views
7

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

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

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

+0

Чтобы устранить основные проблемы этого вопроса относительно обобщенной проблемы преобразования типов в .NET, см. Мой ответ на соответствующий вопрос в разделе [Как преобразовать типы во время выполнения?] (Http://stackoverflow.com/ вопросы/312858/how-can-i-convert-types-at-runtime/7942350 # 7942350) – 2011-10-29 23:43:45

ответ

14

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

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

5

MASSIVE EDIT Вы будете в состоянии сделать это с C# 4.0, но [спасибо Jon]

Вы можете обойти его с помощью ConvertAll:

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

Если я не понял это, вы не будете этого делать. Список не может быть ни ковариантным, ни противоречивым, поскольку он позволяет использовать как «в», так и «вне». – 2008-11-25 13:43:17

+0

+1 для комментария Марка. – 2008-11-25 13:45:41

+0

Вам также нужно будет выбрать тип назначения: return x.ConvertAll (f => f); – 2008-11-25 13:46:50

8

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

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; 
} 
0

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

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

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

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

12

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

// 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 теперь имеет форму в нем. Плохо!

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

0

Если, в то время, 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> и (с использованием структуры, а не класса, будет означать, что код будет избегать необходимости создания новых объектов кучи при кастинге в любом сценарии, в котором структура не должна быть вложена в коробку).

 Смежные вопросы

  • Нет связанных вопросов^_^