2009-03-05 2 views
4

Недавно я высказал свое мнение об этом в другом месте *, но я думаю, что он заслуживает дальнейшего анализа, поэтому я публикую это как свой собственный вопрос.Должен ли я использовать интерфейс типа IEnumerable или конкретный класс, например List <>

Предположим, что мне нужно создать и передать контейнер в моей программе. У меня, вероятно, нет сильного мнения об одном виде контейнера по сравнению с другим, по крайней мере на данном этапе, но я выбираю один; для аргументации, допустим, я собираюсь использовать список <>.

Вопрос заключается в том: ли лучше писать свои методы, чтобы принимать и возвращать высокий интерфейс уровня, таких как С # IEnumerable? Или я должен писать методы для принятия и передачи определенного класса контейнера, который я выбрал.

Какие факторы и критерии я должен искать? Какие программы работают от одного или другого? Влияет ли компьютерный язык на ваше решение? Представление? Размер программы? Личный стиль?

(Есть ли еще вопрос?)

** (Домашнее задание:.. Найти Но пожалуйста, напишите свой ответ здесь, прежде чем искать самостоятельно, так как не предвзятость вас) *

ответ

5

только вещь, которая должна повлиять на ваше решение, - это то, как вы планируете использовать параметр. Если вы только перебираете его, используйте IEnumerable<T>. Если вы обращаетесь к индексированным членам (например, var x = list[3]) или изменяете список любым способом (например, list.Add(x)), используйте ICollection<T> или IList<T>.

16

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

+0

Согласен, но я как раз собирался задать почти тот же вопрос, потому что мой код неловко использует IEnumerable; вместо списка foos = fooQuery.GetFoos(), он становится списком foos = new Список (fooQuery.GetFoos()). Я что-то упускаю? Или это цена, которую мы платим за гибкость? – si618

+0

Пример с использованием приведенного выше комментария - это модульные тесты, в которых вы хотите проверить результаты, например. IList.Count, фактическое использование действительно не требует IList, и IEnumerable будет достаточно, но это просто неудобно ... – si618

+0

@Si Я думаю, что мы говорим о разных вещах здесь. Этот вопрос касается только объема внутри метода; вы говорите о типах возврата и использовании вне метода. –

0

Это имеет значение, но правильное решение полностью зависит от использования. Если вам нужно сделать простое перечисление, тогда обязательно используйте IEnumerable таким образом, чтобы вы могли передать любому разработчику доступ к необходимым функциям. Однако, если вам нужна функция списка, и вы не хотите создавать новый экземпляр списка, если случайно каждый раз, когда метод называется перечислимым, который был передан, не был списком, а затем перейдите к списку.

3

Всегда есть компромисс. Общее правило заключается в том, чтобы объявить вещи максимально возможными по иерархии. Поэтому, если вам нужен только доступ к методам в IEnumerable, то это то, что вы должны использовать.

Еще одним недавним примером SO-запроса был C API, который вместо имени файла * (или файлового дескриптора) принял имя файла. Там имя файла строго ограничено тем, что может быть передано в вещах (есть много вещей, которые вы можете передать файловому дескриптору, но только тот, у которого есть имя файла).

После того, как вы должны начать кастинг, вы либо зашли слишком высоко ИЛИ вы должны сделать второй метод, который принимает более конкретный тип.

Единственным исключением из этого, о котором я могу думать, является то, что скорость является абсолютной необходимостью, и вы не хотите проходить через вызов виртуального метода. Объявление определенного типа устраняет накладные расходы на виртуальные функции (зависит от языка/среды/реализации, но как общего утверждения, которое, скорее всего, правильно).

0

Я ответил на аналогичный вопрос C# here.Я думаю, вы всегда должны предоставить самый простой контракт, который, по моему мнению, обычно является IEnumerable Of T.

Реализация может быть предоставлена ​​внутренним типом BCL - будь то Set, Collection, List и т. д. - чьи требуемые участники подвергаются вашему типу.

Ваш абстрактный тип всегда может наследовать простые типы BCL, которые реализуются вашими конкретными типами. Это, на мой взгляд, позволяет вам придерживаться LSP.

3

Это была дискуссия со мной, которая вызвала этот вопрос, поэтому Евро Мичелли уже знает мой ответ, но вот он! :)

Я думаю, что Linq to Objects уже дает отличный ответ на этот вопрос. Используя простейший интерфейс для последовательности элементов, которые он мог бы, он дает максимальную гибкость в отношении того, как вы реализуете эту последовательность, которая позволяет ленивое поколение, повышая производительность, не жертвуя производительностью (не в каком-либо реальном смысле).

Верно, что преждевременная абстракция может стоить, но в основном это затраты на открытие/изобретательность новых абстракций. Но если у вас уже есть отличные хорошие, предоставленные вам, тогда вы будете сумасшедшими, чтобы не использовать их, и это то, что предоставляет вам общий набор интерфейсов.

Есть те, кто скажет вам, что «проще» сделать все данные в открытом классе, на всякий случай, когда вам нужно будет получить к нему доступ. Точно так же Euro советовал, что лучше использовать богатый интерфейс для контейнера, такого как IList<T> (или даже конкретный класс List<T>), а затем очистить беспорядок позже.

Но я думаю, что лучше скрыть члены данных класса, к которому вы не хотите обращаться, чтобы вы могли легко модифицировать реализацию этого класса, поэтому вы должны использовать простейший интерфейс доступный для обозначения последовательности элементов. На практике проще начинать с того, что выставляете что-то простое и основное, а затем «ослабляете» его позже, чем начинать с чего-то свободного и пытаться навязать ему порядок.

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

Таким образом, будет совершенно ясно (только при локальном изучении какого-либо кода) именно то, что этот код сможет сделать с данными.

Маленькие программы требуют меньше абстракции, это правда. Но если они успешны, они, как правило, становятся большими программами. Это намного проще, если они используют простые абстракции в первую очередь.