Когда использовать каждый из T [], Список <T>, IEnumerable <T>?

Я обычно делаю что-то как:

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 и массивами и массивами и Списками или разве который не идиоматичен (существуют лучшие способы сделать это) / не производительный (не обычно релевантный, но мог бы быть в некоторых случаях) / просто ужасный (unmaintable, нечитабельный)?

8
задан Vinko Vrsalovic 4 August 2010 в 23:45
поделиться

7 ответов

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

  • Преобразование из списка в 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 [] values). Но если бы это был PrintValues ​​(значения IEnumerable ), он мог бы иметь дело со списками, T [], стеками, хэш-таблицами, словарями, строками, наборами и т. Д. - любой коллекцией, которая реализует IEnumerable, что практически каждый коллекция.

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

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

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

8
ответ дан 5 December 2019 в 09:23
поделиться

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

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

1
ответ дан 5 December 2019 в 09:23
поделиться

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

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

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

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

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

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

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

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

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

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

4
ответ дан 5 December 2019 в 09:23
поделиться

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

Я бы посоветовал вернуть наиболее конкретный тип и выбрать наиболее гибкий тип.

Примерно так:

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

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

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

0
ответ дан 5 December 2019 в 09:23
поделиться

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

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

0
ответ дан 5 December 2019 в 09:23
поделиться

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

7
ответ дан 5 December 2019 в 09:23
поделиться

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

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

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

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

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

-2
ответ дан 5 December 2019 в 09:23
поделиться
Другие вопросы по тегам:

Похожие вопросы: