Когда использовать каждый из T [], List <T>, IEnumerable <T>? - PullRequest
8 голосов
/ 05 августа 2010

Я обычно замечаю, что делаю что-то вроде:

string[] things = arrayReturningMethod();
int index = things.ToList<string>.FindIndex((s) => s.Equals("FOO"));
//do something with index
return things.Distinct(); //which returns an IEnumerable<string>

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

Является ли этот идиоматичный и правильный C # или есть лучшая альтернатива, чтобы избежать приведения туда-сюда для доступа к правильным методам работы с данными?

РЕДАКТИРОВАТЬ: Вопрос на самом деле двойной:

  • Когда уместно использовать интерфейс IEnumerable, массив или список (или любой другой тип реализации IEnumerable) напрямую (при принятии параметров)?

  • Если вы свободно перемещаетесь между IEnumerables (реализация неизвестна) и списками и IEnumerables, массивами, массивами и списками, или это не идиоматично (есть лучшие способы сделать это) / неэффективно (обычно не актуально, но может быть вв некоторых случаях) / просто безобразно (неосновно, нечитаемо)?

Ответы [ 7 ]

7 голосов
/ 05 августа 2010

Что касается производительности ...

  • Преобразование из списка в T [] включает в себя копирование всех данных из исходного списка во вновь выделенный массив.
  • Преобразование из T [] в Список также включает в себя копирование всех данных из исходного списка во вновь распределенный Список.
  • Преобразование из List или T [] в IEnumerable включает приведение, что составляет несколько циклов ЦП.
  • Преобразование из IEnumerable в List включает в себя обновление, что также составляет несколько циклов ЦП.
  • Преобразование из IEnumerable в T [] также включает апкастинг.
  • Вы не можете привести IEnumerable к T [] или List, если только это не был T [] или List соответственно для начала. Вы можете использовать функции ToArray или ToList, но они также приведут к созданию копии.
  • Доступ ко всем значениям в порядке от начала до конца в T [] в прямом цикле будет оптимизирован для использования простой арифметики указателей - что делает его самым быстрым из всех.
  • Доступ ко всем значениям по порядку от начала до конца в Списке включает проверку каждой итерации, чтобы убедиться, что у вас нет доступа к значению за пределами массива, а затем фактический доступ к значению массива.
  • Доступ ко всем значениям в IEnumerable включает создание объекта-перечислителя, вызов функции Next (), которая увеличивает указатель индекса, а затем вызов свойства Current, которое дает вам фактическое значение и помещает его в переменную, указанную вами в ваше заявление foreach. Как правило, это не так плохо, как кажется.
  • Доступ к произвольному значению в IEnumerable предполагает запуск с начала и вызов Next () столько раз, сколько вам нужно, чтобы получить это значение. Как правило, это так плохо, как кажется.

В отношении идиом ...

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

Например, если бы у вас была функция PrintValues, если бы она была записана как PrintValues ​​(List values), она могла бы работать только со значениями List, поэтому пользователю сначала пришлось бы преобразовать, если, например, они использовали T []. Аналогично, если функция была PrintValues ​​(значения T []). Но если бы это были PrintValues ​​(IEnumerable values), он мог бы иметь дело со списками, T [], стеками, хеш-таблицами, словарями, строками, наборами и т. Д. - любой коллекцией, реализующей IEnumerable, которая есть практически у каждого коллекция.

Что касается внутреннего использования ...

  • Используйте список только в том случае, если вы не уверены, сколько предметов должно быть в нем.
  • Используйте T [], если вы знаете, сколько элементов должно быть в нем, но вам нужен доступ к значениям в произвольном порядке.
  • Придерживайтесь IEnumerable, если это то, что вам дали, и вам просто нужно использовать его последовательно. Многие функции будут возвращать IEnumerables. Если вам нужен доступ к значениям из IEnumerable в произвольном порядке, используйте ToArray ().

Также обратите внимание, что приведение отличается от использования ToArray () или ToList () - последнее включает в себя копирование значений, что действительно приводит к снижению производительности и памяти, если у вас много элементов. Первый просто сказать, что «собака - это животное, поэтому, как и любое животное, оно может есть» (удрученно) или «это животное - собака, поэтому оно может лаять» (повышенное настроение). Аналогично, все списки и T [] являются IEnumerables, но только некоторые IEnumerables являются списками или T [] s.

7 голосов
/ 05 августа 2010

Хорошее эмпирическое правило: всегда используйте IEnumerable (при объявлении переменных / параметров метода / возвращаемых типов метода / свойств / и т. Д.), Если у вас нет веской причины не делать этого. Безусловно, наиболее совместимый по типу с другими (особенно расширенными) методами.

3 голосов
/ 05 августа 2010

Ну, у вас есть два яблока и апельсин, который вы сравниваете.

Два яблока - это массив и список.

  • Массив в C # - это массив в стиле C, в который встроена сборка мусора. Преимущество их использования в том, что они имеют очень мало накладных расходов, при условии, что вам не нужно перемещать вещи. Плохо то, что они не так эффективны, когда вы добавляете, удаляете и иным образом изменяете массив, так как память перемешивается.

  • Список - это динамический массив в стиле C # (аналогично классу vector <> в C ++). Существует больше накладных расходов, но они более эффективны, когда вам нужно много перемещать, поскольку они не будут пытаться поддерживать непрерывное использование памяти.

Лучшее сравнение, которое я могу дать, это сказать, что массивы относятся к спискам, а строки - к StringBuilders.

Апельсин - это «IEnumerable». Это не тип данных, а скорее интерфейс. Когда класс реализует интерфейс IEnumerable, он позволяет использовать этот объект в цикле foreach ().

Когда вы возвращаете список (как вы это сделали в своем примере), вы не конвертировали список в IEnumerable. Список уже является объектом IEnumerable.

РЕДАКТИРОВАТЬ: Когда преобразовать между двумя:

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

Надеюсь, это немного рассеет туман.

1 голос
/ 05 августа 2010

Мне кажется, проблема в том, что вы не удосужились научиться искать в массиве. Подсказка: Array.IndexOf или Array.BinarySearch в зависимости от того, отсортирован ли массив.

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

0 голосов
/ 05 августа 2010

Когда использовать что?

Я бы предложил вернуть наиболее конкретный тип и взять наиболее гибкий тип.

Как это:

public int[] DoSomething(IEnumerable<int> inputs)
{
    //...
}

public List<int> DoSomethingElse(IList<int> inputs)
{
    //...
}

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

0 голосов
/ 05 августа 2010

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

Мой общий подход:

  1. Используйте T [] для информации в стиле «статика» или «снимок».Используйте для вещей, где вызов .Add () не имеет смысла, и вам не нужны дополнительные методы, которые вам предоставляет List .
  2. Accept IEnumerable , если вас это не волнуетто, что вам дано и вам не нужно постоянное время. Длина / .Count.
  3. Возвращайте IEnumerable , только когда вы делаете простые манипуляции с входом IEnumerable или когда вы специальнохотите использовать синтаксис yield для ленивой работы.
  4. Во всех остальных случаях используйте List .Это слишком гибко.

Следствие к # 4: не бойтесь ToList ().ToList () ваш друг.Это заставляет IEnumerable вычислять прямо сейчас (полезно, когда вы складываете несколько предложений where).Не сходите с ума, но не стесняйтесь называть его, как только вы соберете полное предложение where, прежде чем выполнять foreach над ним (или тому подобное).

Конечно, это простоГрубый ориентир.Просто попробуйте следовать тому же шаблону в той же кодовой базе - стили кода, которые меняются, усложняют работу программистов по обслуживанию.

0 голосов
/ 05 августа 2010

Я стараюсь избегать быстрого перехода между типами данных, если этого можно избежать.

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

...