Массив по сравнению со списком <T>: когда и какой использовать?

594
MyClass[] array;
List<MyClass> list;

Каковы сценарии, когда один предпочтительнее другого? И почему?

Фредерик Дурак
источник
9
Массивы довольно устарели, как видно из популярной дискуссии здесь. Также указано здесь и нашим хозяином в блоге .
Гимел
9
Если я не ошибаюсь, List <> имеет массив как внутреннюю структуру. Всякий раз, когда внутренний массив заполняется, он просто копирует содержимое в массив, который имеет двойной размер (или некоторую другую постоянную, умноженную на текущий размер). ru.wikipedia.org/wiki/Dynamic_array
Икок,
Ykok: То, что вы говорите, кажется правильным, я нашел исходный код List <> здесь .
Кэрол
19
@gimel Утверждать, что массивы устарели, возможно, немного смело
awdz9nld

Ответы:

580

На самом деле редко, когда вы захотите использовать массив. Определенно используйте List<T>любое время, когда вы хотите добавить / удалить данные, так как изменение размеров массивов стоит дорого. Если вы знаете, что данные имеют фиксированную длину, и вы хотите микрооптимизировать по какой-то очень конкретной причине (после сравнительного анализа), тогда может пригодиться массив.

List<T>предлагает намного больше функциональности, чем массив (хотя LINQ немного выравнивает его), и почти всегда является правильным выбором. За исключением paramsаргументов, конечно. ;-п

Как счетчик - List<T>одномерен; где-как у вас есть прямоугольные (и т. д.) массивы, такие как int[,]или string[,,]- но есть другие способы моделирования таких данных (если вам нужно) в объектной модели.

Смотрите также:

Тем не менее, я много использую массивы в своем проекте protobuf-net ; полностью для производительности:

  • он сильно сдвигает биты, поэтому byte[]он очень важен для кодирования;
  • Я использую локальный byte[]буферный буфер, который я заполняю перед отправкой вниз в основной поток (и vv); быстрее чем и BufferedStreamт.д .;
  • он внутренне использует основанную на массиве модель объектов ( Foo[]а не List<Foo>), так как размер устанавливается после сборки и должен быть очень быстрым.

Но это определенно исключение; для общей бизнес-обработки, List<T>выигрывает каждый раз.

Марк Гравелл
источник
8
Аргумент об изменении размера полностью действителен. Однако люди предпочитают списки, даже если изменение размера не требуется. Для этого последнего случая, есть ли твердый, логичный аргумент или это не что иное, как «массивы вышли из моды»?
Фредерик Дурак
6
«Обязательно используйте List <T> в любое время, когда хотите добавить / удалить данные, поскольку изменение размеров массивов стоит дорого». List <T> использует массив внутри. Вы думали о LinkedList <T>?
Дан-г-н
14
Дополнительные функции == более сложные == не хорошо, если вам не нужны эти функции. Этот ответ в основном перечисляет причины, почему массивы лучше, но делает противоположный вывод.
Имон Нербонн
12
@EamonNerbonne, если вы не используете эти функции, я могу в значительной степени гарантировать, что они не причинят вам вреда ... но: по моему опыту, количество коллекций, которые никогда не нуждаются в мутации, намного меньше, чем тех, которые мутировал
Марк Гравелл
7
@MarcGravell: это зависит от вашего стиля кодирования. По моему опыту, практически ни одна коллекция не была видоизменена. Это; коллекции извлекаются из базы данных или создаются из какого-либо источника, но дальнейшая обработка всегда выполняется путем воссоздания новой коллекции (например, карты / фильтра и т. д.). Даже там, где необходима концептуальная мутация, проще всего просто создать новую коллекцию. Я только изменяю коллекцию как оптимизацию производительности, и такие оптимизации, как правило, носят локальный характер и не подвергают мутации потребителям API.
Имон Нербонн
121

На самом деле, просто отвечая на добавление ссылки, о которой я удивлен, пока не упоминалось: запись Эрика Липперта в блоге «Массивы считаются несколько вредными».

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

Джон Скит
источник
2
Наконец-то дошло до чтения через 3 года, ха-ха. Хорошая статья тогда, хорошая статья сейчас. :)
Спенсер Рупорт
21

Несмотря на рекомендации других ответов List<T>, вы захотите использовать массивы при обработке:

  • данные растрового изображения
  • другие низкоуровневые структуры данных (например, сетевые протоколы)
Альнитак
источник
1
Почему для сетевых протоколов? Не лучше ли использовать здесь пользовательские структуры и дать им специальный сериализатор или явный макет памяти? Кроме того, что говорит против использования List<T>здесь, а не байтового массива?
Конрад Рудольф
8
@Konrad - ну, для начала, Stream.Read и Stream.Write работают с байтом [], как и кодирование и т. Д.
Марк Гравелл
12

Если вы действительно не заинтересованы в производительности, и под этим я подразумеваю: «Почему вы используете .Net вместо C ++?» Вы должны придерживаться списка <>. Это проще в обслуживании и выполняет всю грязную работу по изменению размера массива за кулисами для вас. (При необходимости List <> довольно умно подходит к выбору размеров массива, поэтому обычно в этом нет необходимости.)

Спенсер Рупорт
источник
15
«Почему вы используете .Net вместо C ++?» XNA
Bengt
6

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

Например,

var str = "This is a string";
var strChars = str.ToCharArray();  // returns array

Ясно, что модификация "strChars" не будет изменять исходный объект "str", независимо от уровня знаний о реализации базового типа "str".

Но предположим, что

var str = "This is a string";
var strChars = str.ToCharList();  // returns List<char>
strChars.Insert(0, 'X');

В этом случае не ясно только из этого фрагмента кода, будет ли метод вставки изменять или не изменять исходный объект "str". Требуется знание уровня реализации String, чтобы сделать это определение, что нарушает подход «Проектирование по контракту». В случае с String это не имеет большого значения, но может иметь большое значение практически во всех остальных случаях. Установка списка только для чтения помогает, но приводит к ошибкам во время выполнения, а не во время компиляции.

Герман Шенфельд
источник
Я относительно новичок в C #, но мне непонятно, почему возвращение списка предполагает изменчивость исходных данных так, как это невозможно при возврате массива. Я хотел бы иметь, однако, что метод, чье имя начинается с To, собирается создать объект, который не имеет возможности изменять исходный экземпляр, в отличие от strChars as char[]которого, если действительный, предполагает, что вы теперь можете изменить исходный объект.
Тим МБ
@TimMB Существует неизменность коллекции (нельзя добавлять или удаленные элементы) и неизменность элементов в коллекции. Я имел в виду последнее, а вы, вероятно, смешиваете два. Возвращение массива гарантирует клиенту, что он не может добавлять / удалять элементы. Если это так, он перераспределяет массив и уверен, что это не повлияет на оригинал. Возвращая список, такие гарантии не делаются, и оригинал может быть затронут (зависит от реализации). Изменение элементов в коллекции (массив или список) может повлиять на оригинал, если тип элемента не является структурой.
Герман Шенфельд
Спасибо за разъяснение. Я все еще в замешательстве (вероятно, потому что я из мира C ++). Если strвнутренне использует массив и ToCharArrayвозвращает ссылку на этот массив, клиент может strизменить его, изменив элементы этого массива, даже если размер остается фиксированным. Тем не менее, вы пишете «Понятно, что модификация« strChars »не будет изменять исходный объект« str »». Что мне здесь не хватает? Из того, что я вижу, в любом случае клиент может иметь доступ к внутреннему представлению, и, независимо от типа, это допускает мутации какого-либо рода.
Тим МБ
3

Если я точно знаю , сколько элементов я буду нуждаться, скажем , мне нужно 5 элементов , и только когда - либо 5 элементов , то я использую массив. В противном случае я просто использую список <T>.

smack0007
источник
1
Почему бы вам не использовать List <T> в том случае, если вы знаете количество элементов?
Оливер
3

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

См. Http://msdn.microsoft.com/en-us/library/ms379570(v=vs.80).aspx#datastructures20_1_topic5 для получения дополнительной информации о списках в C # или просто для декомпиляции System.Collections.Generic.List<T>.

Если вам нужны многомерные данные (например, с использованием матрицы или в графическом программировании), вы, вероятно, предпочтете использовать arrayвместо них.

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

Sune Rievers
источник
1
Привет, не могли бы вы объяснить, почему «Время поиска в списке будет O (n)» верно? Насколько я знаю, List <T> использует массив за кулисами.
стрекоза
1
@ дракон, ты совершенно прав. Источник . В то время я предполагал, что в реализации используются указатели, но с тех пор я узнал иначе. По ссылке выше: «Получение значения этого свойства является операцией O (1); установка свойства также является операцией O (1). '
Sune Rievers
2

Массивы Vs. Списки - это классическая проблема обслуживания и производительности. Практическое правило, которому следуют почти все разработчики, заключается в том, что вы должны использовать оба подхода, но когда они вступают в конфликт, выбирайте удобство обслуживания вместо производительности. Исключением из этого правила является ситуация, когда производительность уже оказалась проблемой. Если вы несете этот принцип в Arrays Vs. Списки, то, что вы получите, это:

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

Мельбурн Разработчик
источник
1

Еще одна ситуация, которая еще не упомянута, - это когда у одного будет большое количество элементов, каждый из которых состоит из фиксированной группы связанных, но независимых переменных, слипшихся воедино (например, координаты точки или вершины трехмерного треугольника). Массив структур с открытыми полями позволит эффективно модифицировать его элементы «на месте», что невозможно для любого другого типа коллекции. Поскольку массив структур хранит свои элементы последовательно в ОЗУ, последовательный доступ к элементам массива может быть очень быстрым. В ситуациях, когда код должен будет выполнить много последовательных проходов через массив, массив структур может превзойти массив или другую коллекцию ссылок на объекты класса в 2: 1; дальше,

Хотя размеры массивов не изменяются, нетрудно заставить код хранить ссылку на массив вместе с количеством используемых элементов, и при необходимости заменить массив на больший. В качестве альтернативы, можно легко написать код для типа, который ведет себя очень похоже на, List<T>но предоставляет доступ к его резервному хранилищу, что позволяет сказать либо MyPoints.Add(nextPoint);или, либо MyPoints.Items[23].X += 5;. Обратите внимание, что последний не обязательно вызовет исключение, если код попытается получить доступ за пределами списка, но в противном случае использование будет концептуально очень похоже на List<T>.

Supercat
источник
То, что вы описали, это список <>. Есть индексатор, так что вы можете напрямую обращаться к базовому массиву, а List <> будет поддерживать размер для вас.
Карл
@Carl: Учитывая, например Point[] arr;, код может сказать, например arr[3].x+=q;. Используя, например List<Point> list, было бы необходимо вместо этого сказать Point temp=list[3]; temp.x+=q; list[3]=temp;. Было бы полезно, если List<T>бы был метод Update<TP>(int index, ActionByRefRef<T,TP> proc, ref TP params). и компиляторы могут превратиться list[3].x+=q;в , {list.Update(3, (ref int value, ref int param)=>value+=param, ref q);но нет такой функции не существует.
суперкат
Хорошие новости. Оно работает. list[0].X += 3;добавит 3 к свойству X первого элемента списка. И listявляется List<Point>и Pointявляется классом со свойствами X и Y
Карл
1

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

iliketocode
источник
0

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

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

moarboilerplate
источник
0

Они могут быть непопулярны, но я фанат Arrays в игровых проектах. - Скорость итерации может быть важна в некоторых случаях, потому что foreach на массиве имеет значительно меньшие накладные расходы, если вы не делаете много для каждого элемента - Добавление и удаление не так сложны с вспомогательными функциями - Это медленнее, но в случаях, когда вы строите его только один раз это может не иметь значения - В большинстве случаев меньше дополнительной памяти тратится впустую (действительно очень важно для массивов структур) - Немного меньше мусора и указателей и погоня за указателями

При этом я использую List гораздо чаще, чем Arrays на практике, но у каждого из них есть свое место.

Было бы неплохо, если бы в List был встроенный тип, чтобы они могли оптимизировать накладные расходы и издержки перечисления.


источник
0

Заполнить список проще, чем массив. Для массивов вам нужно знать точную длину данных, но для списков размер данных может быть любым. И вы можете преобразовать список в массив.

List<URLDTO> urls = new List<URLDTO>();

urls.Add(new URLDTO() {
    key = "wiki",
    url = "https://...",
});

urls.Add(new URLDTO()
{
    key = "url",
    url = "http://...",
});

urls.Add(new URLDTO()
{
    key = "dir",
    url = "https://...",
});

// convert a list into an array: URLDTO[]
return urls.ToArray();
Бимал Пудель
источник
0

Поскольку никто не упоминает: в C # массив представляет собой список. MyClass[]и List<MyClass>оба реализуют IList<MyClass>. (например, void Foo(IList<int> foo)можно назвать как Foo(new[] { 1, 2, 3 })илиFoo(new List<int> { 1, 2, 3 }) )

Итак, если вы пишете метод, который принимает a List<MyClass>в качестве аргумента, но использует только подмножество функций, вы можете объявить какIList<MyClass> вместо этого для удобства вызывающих.

Подробности:

snipsnipsnip
источник
«В C # массив - это список» Это не так; массив не является List, он только реализует IListинтерфейс.
Руфус Л
-1

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

Теперь, если у вас есть список элементов, и вы просто хотите отобразить их, скажем, в массиве веб-страниц есть контейнер, который вам нужно использовать.

sajidnizami
источник
1
Если у вас есть список элементов, и вы просто хотите отобразить их, то что плохого в том, чтобы просто использовать список, который у вас уже есть? Что бы здесь предлагал массив?
Марк Гравелл
1
А для «создания элементов, которые будут использоваться другими функциями или службами», на самом деле, я бы предпочел блок итератора с IEnumerable<T>- тогда я могу передавать объекты в потоковом режиме, а не буферизировать их.
Марк Гравелл
-1

Стоит упомянуть о способности делать кастинг на месте.

interface IWork { }
class Foo : IWork { }

void Test( )
{
    List<Foo> bb = new List<Foo>( );
    // Error: CS0029 Cannot implicitly convert type 'System.Collections.Generic.List<Foo>' to 'System.Collections.Generic.List<IWork>'
    List<IWork> cc = bb; 

    Foo[] bbb = new Foo[4];
    // Fine
    IWork[] ccc = bbb;
}

Поэтому Array предлагает немного больше гибкости при использовании в типе возвращаемого значения или аргументе для функций.

IWork[] GetAllWorks( )
{
    List<Foo> fooWorks = new List<Foo>( );
    return fooWorks.ToArray( ); // Fine
}

void ExecuteWorks( IWork[] works ) { } // Also accept Foo[]
Wappenull
источник
Следует отметить , что дисперсия массива будет нарушена .
Маккартон