Масив проти списку <T>: коли використовувати?


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

Які сценарії, коли один є кращим за інший? І чому?


9
Масиви є досить застарілими, як це видно в популярній дискусії тут. Також на це вказував і наш господар у блозі .
gimel

9
Якщо я не помиляюся, Список <> має масив як внутрішню структуру. Щоразу, коли внутрішній масив заповнений, він просто скопіює вміст у масив, що подвоюється за розміром (або якийсь інший постійний раз від поточного розміру). en.wikipedia.org/wiki/Dynamic_array
Ykok

Ykok: Те, що ви говорите, здається правильним, я знайшов вихідний код Список <> тут .
Керрол

19
@gimel Стверджувати, що масиви застаріли, можливо, трохи сміливі
awdz9nld

Відповіді:


580

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

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

Як лічильник - List<T>є одновимірним; де - як у вас є прямокутні (тощо) масиви на зразок int[,]або string[,,]- але є інші способи моделювання таких даних (якщо вам потрібно) в об'єктній моделі.

Дивись також:

Це означає, що я багато використовую масиви в своєму проекті протобуф-нетто ; повністю для продуктивності:

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

Але це, безумовно, виняток; для загальної обробки бізнесу, List<T>виграш кожного разу.


8
Аргумент про зміну розміру цілком справедливий. Однак люди віддають перевагу спискам навіть тоді, коли не потрібно змінювати розміри. Чи є для цього останнім випадком солідний логічний аргумент чи це не що інше, як "масиви не в моді"?
Фредерік Дурень

6
"Однозначно використовуйте Список <T> будь-коли, коли ви хочете додати / видалити дані, оскільки розмір масивів дорогий." Список <T> використовує масив внутрішньо. Ви думали про LinkedList <T>?
дан-gph

14
Більше можливостей == складніше == не добре, якщо вам ці функції не потрібні. Ця відповідь, в основному, перераховує причини, чому масиви кращі, але робить зворотний висновок.
Еймон Нербонна

12
@EamonNerbonne, якщо ти не використовуєш ці функції, я можу гарантувати, що вони не зашкодять тобі ... але: кількість моїх колекцій, які ніколи не потребують мутації, набагато менша, ніж ті, що є мутував
Marc Gravell

7
@MarcGravell: це залежить від вашого стилю кодування. На мій досвід, фактично жодна колекція не змінюється. Це є; колекції витягуються з бази даних або будуються з якогось джерела, але подальша обробка завжди проводиться шляхом відтворення нової колекції (наприклад, карта / фільтр тощо). Навіть там, де концептуальні мутації необхідні, це, як правило, найпростіше просто створити нову колекцію. Я коли-небудь мутую колекцію як оптимізацію продуктивності, і такі оптимізації, як правило, є дуже локальними і не піддають мутацію споживачам API.
Еймон Нербонна

121

Дійсно, лише відповівши, щоб додати посилання, яке я здивований, ще не було згадано: запис у блозі Еріка Ліпперта на тему "Масиви вважаються дещо шкідливими".

З назви можна судити, що він пропонує використовувати колекції там, де це практично - але, як справедливо зазначає Марк, є багато місць, де масив справді є єдиним практичним рішенням.


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

21

Незважаючи на інші відповіді, що рекомендують List<T>, вам потрібно буде використовувати масиви під час обробки:

  • дані растрових зображень зображення
  • інші структури даних низького рівня (тобто мережеві протоколи)

1
Чому для мережевих протоколів? Ви б не скористалися тут спеціальними структурами і не надали їм спеціальний серіалізатор або явний макет пам'яті? Крім того, що говорить проти використання List<T>тут, а не байтового масиву?
Конрад Рудольф

8
@Konrad - ну для початку, Stream.Read і Stream.Write працювати з байтом [], як і Encoding etc. ...
Марк Гравелл

12

Якщо ви дійсно не переймаєтесь продуктивністю, і я маю на увазі: "Для чого ви використовуєте .Net замість C ++?" вам слід дотримуватися списку <>. Це простіше в обслуговуванні та виконує всю брудну роботу щодо зміни розміру масиву за кадром для вас. (Якщо потрібно, Список <> досить розумний щодо вибору розмірів масиву, тому зазвичай не потрібно.)


15
"Чому ви використовуєте .Net замість C ++?" XNA
Бенгт

6

Масиви слід використовувати в перевазі «Список», коли незмінність самої колекції є частиною договору між кодом клієнта та постачальника (не обов'язково незмінність елементів у колекції) І коли 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>.


1
Чому б ви не використовували Список <T> у випадку, коли вам відомо кількість елементів?
Олівер

3

У більшості випадків використання a 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замість цього.

Як завжди, якщо пам'ять чи продуктивність є проблемою, виміряйте це! В іншому випадку ви можете робити помилкові припущення щодо коду.


1
Привіт, ви могли б пояснити, чому "час пошуку списку було б O (n)"? Наскільки я знаю, Список <T> використовує масив за кадром.
стрікоза

1
@dragonfly ви абсолютно праві. Джерело . У той час я припускав, що реалізація використовує покажчики, але з тих пір я дізнався інакше. З посилання вище: "Отримання значень цього властивості є операцією O (1); встановлення властивості також є операцією O (1). '
Sune Rievers

2

Масиви Vs. Списки - це класична проблема технічного обслуговування та продуктивності. Основне правило, яке дотримуються майже всі розробники, полягає в тому, що ви повинні стріляти для обох, але коли вони вступають у конфлікт, вибирайте ремонтопридатність над продуктивністю. Виняток із цього правила є тоді, коли результативність вже виявила проблему. Якщо ви застосовуєте цей принцип у Arrays Vs. Списки, то ви отримуєте це:

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


1

Ще одна ситуація, про яку ще не було сказано, - це коли у вас буде велика кількість елементів, кожен з яких складається з фіксованої купи пов'язаних між собою незалежних змінних, скріплених між собою (наприклад, координати точки або вершини 3d трикутника). Масив структур відкритого поля дозволить ефективно змінювати його елементи "на місці" - те, що неможливо з будь-яким іншим типом колекції. Оскільки масив структур утримує свої елементи послідовно в оперативній пам'яті, послідовний доступ до елементів масиву може бути дуже швидким. У ситуаціях, коли коду потрібно буде зробити безліч послідовних проходів через масив, масив структур може перевершити масив або іншу колекцію посилань на об'єкт класу на коефіцієнт 2: 1; далі,

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


Те, що ви описали, - це Список <>. Існує індексатор, щоб ви могли отримати доступ до базового масиву безпосередньо, і Список <> збереже розмір для вас.
Карл

@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);але немає такої функції не існує.
supercat

Хороші новини. Це працює. list[0].X += 3;додасть 3 до властивості X першого елемента списку. А listє List<Point>і Pointє класом із властивостями X та Y
Карл

1

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


0

Замість того, щоб провести порівняння особливостей кожного типу даних, я вважаю, що найбільш прагматична відповідь полягає в тому, що "відмінності, ймовірно, не такі важливі для того, що потрібно досягти, тим більше, що вони обидва реалізують IEnumerable, тому дотримуйтесь загальноприйнятої конвенції та використовуйте Listпоки у вас немає причини цього не робити, і тоді ви, мабуть, матимете свою причину для використання масиву над знаком List".

Більшу частину часу в керованому коді ви хочете віддавати перевагу колекціям як можна простіше працювати з можливістю, турбуючись про мікрооптимізацію.


0

Вони можуть бути непопулярними, але я прихильник масивів у ігрових проектах. - Швидкість ітерації може бути важливою в деяких випадках, якщо передбачення на масиві має значно менші накладні витрати, якщо ви не робите багато за один елемент - додавання та видалення не так вже й важко за допомогою помічників - це повільніше, але у випадках, коли ви збираєте його лише один раз це може не мати значення - У більшості випадків витрачається менше зайвої пам’яті (лише справді важливо для масивів структур) - трохи менше сміття та покажчиків та погоні за вказівниками

Коли це говориться, я використовую 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 }))

Отже, якщо ви пишете метод, який приймає List<MyClass>як аргумент, але використовує лише підмножину функцій, ви можете IList<MyClass>замість цього оголосити як зручність для абонентів.

Деталі:


"У C # масив - це список" Це неправда; масив не є List, він реалізує лише IListінтерфейс.
Руфус L

-1

Це повністю залежить від контекстів, в яких потрібна структура даних. Наприклад, якщо ви створюєте елементи, які будуть використовуватися іншими функціями або службами, використовуючи List, це ідеальний спосіб її виконання.

Тепер, якщо у вас є список елементів, і ви просто хочете їх відобразити, скажімо, на масиві веб-сторінок є контейнер, який потрібно використовувати.


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[]

Слід зазначити , що дисперсія масиву буде порушена .
mcarton
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.