Як ініціалізувати Список <T> до заданого розміру (на відміну від ємності)?


111

.NET пропонує контейнер із загальним списком, продуктивність якого майже однакова (див. Питання Performance of Arrays vs. Lists). Однак при ініціалізації вони досить різні.

Масиви дуже легко ініціалізувати зі значенням за замовчуванням, і за визначенням вони вже мають певний розмір:

string[] Ar = new string[10];

Що дозволяє безпечно призначити випадкові предмети, скажімо:

Ar[5]="hello";

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

List<string> L = new List<string>(10);
for (int i=0;i<10;i++) L.Add(null);

або

string[] Ar = new string[10];
List<string> L = new List<string>(Ar);

Який би був чистіший спосіб?

EDIT: На сьогодні відповіді стосуються можливості, яка є чимось іншим, ніж попереднє заповнення списку. Наприклад, у щойно створеному списку місткістю 10, цього не можна робитиL[2]="somevalue"

EDIT 2: Люди задаються питанням, чому я хочу використовувати списки таким чином, оскільки це не той спосіб, яким вони призначені для використання. Я бачу дві причини:

  1. Можна досить переконливо стверджувати, що списки - це масиви "наступного покоління", додаючи гнучкість майже без штрафу. Тому слід використовувати їх за замовчуванням. Я вказую, що їх може бути не так просто ініціалізувати.

  2. Зараз я пишу базовий клас, який пропонує функціонал за замовчуванням як частину більш широкої основи. У пропонованій мені програмі за замовчуванням розмір списку відомий розширено, тому я міг би використовувати масив. Однак я хочу запропонувати будь-якому базовому класу можливість його динамічно розширити, і тому я вибираю список.


1
"EDIT: На сьогодні відповіді стосуються потенціалу, який є чимось іншим, ніж попереднє заповнення списку. Наприклад, у списку, щойно створеному з ємністю 10, не можна робити L [2] =" деяка вартість "" Враховуючи це модифікація, можливо, ви повинні переробити назву питання ...
aranasaurus

Але в чому полягає користь попереднього заповнення списку з порожніми значеннями, адже це те, що намагається зробити тематичний запуск?
Фредерік Гейселс

1
Якщо відображення позицій є таким важливим, чи не було б більше сенсу використовувати словник <int, string>?
Грег Д

7
Listне є заміною Array. Вони вирішують виразно окремі проблеми. Якщо ви хочете фіксованого розміру, ви хочете Array. Якщо ви використовуєте a List, ви робите це неправильно.
Zenexer

5
Я завжди знаходжу відповіді, які намагаються забити аргументи на кшталт "Я не бачу, чому мені колись знадобиться ...", що обтяжують. Це означає лише те, що ви його не бачили. Це не обов'язково означає щось інше. Я поважаю, що люди хочуть запропонувати кращі підходи до проблеми, але це слід висловлювати більш смиренно, наприклад, "Ви впевнені, що вам потрібен список? Можливо, якщо б ви розповіли більше про свою проблему ...". Таким чином це стає приємним, привабливим та заохочує ОП покращити їх питання. Бути переможцем - будь покірним.
AnorZaken

Відповіді:


79

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

public static class Lists
{
    public static List<T> RepeatedDefault<T>(int count)
    {
        return Repeated(default(T), count);
    }

    public static List<T> Repeated<T>(T value, int count)
    {
        List<T> ret = new List<T>(count);
        ret.AddRange(Enumerable.Repeat(value, count));
        return ret;
    }
}

Ви можете використовувати, Enumerable.Repeat(default(T), count).ToList()але це було б неефективно через зміну розміру буфера.

Зауважте, що якщо Tтип посилань, він зберігатиме countкопії посилань, переданих для valueпараметра, тому всі вони будуть посилатися на один і той же об'єкт. Це може бути або не бути тим, що ви хочете, залежно від випадку використання.

EDIT: Як зазначено в коментарях, ви можете Repeatedскористатися циклом, щоб заповнити список, якщо хочете. Це теж було б трохи швидше. Особисто я знаходжу код, використовуючи Repeatбільш описовий характер, і підозрюю, що в реальному світі різниця в продуктивності була б неактуальною, але ваш пробіг може відрізнятися.


1
Я усвідомлюю, що це старий пост, але мені цікаво. Численні. Повторіть тарифи набагато гірше порівняно з циклом для циклу, як за останньою частиною посилання ( dotnetperls.com/initialize-array ). Також AddRange () має складність O (n) відповідно до msdn. Хіба це не чимало лічильник продуктивного використовувати дане рішення замість простого циклу?
Джиммі

@Jimmy: Обидва підходи будуть O (n), і я вважаю цей підхід більш описовим для того, що я намагаюся досягти. Якщо ви віддаєте перевагу циклу, сміливо використовуйте його.
Джон Скіт

@ Jimmy: Також зауважте, що тест, який там використовується Enumerable.Repeat(...).ToArray(), є не тим, як я його використовую.
Джон Скіт

1
Я використовував Enumerable.Repeat()наступний спосіб (реалізований так Pairсамо, як і еквівалент C ++): Enumerable.Repeat( new Pair<int,int>(int.MaxValue,-1), costs.Count)помітивши побічний ефект, що Listповна копія посилань на один об'єкт. Зміна елемента на зразок myList[i].First = 1змінила кожен елемент в цілому List. Щоб знайти цю помилку, мені знадобилися години. Чи знаєте ви, будь-яке рішення цього питання (крім простого використання спільного циклу та використання .Add(new Pair...)?
00zetti

1
@ Pac0, я просто додав відповідь нижче. Зміни можна відхилити.
Джеремі Рей Браун

136
List<string> L = new List<string> ( new string[10] );

3
особисто я думаю, що це найчистіший спосіб - хоча це було зазначено в питанні як потенційно незграбне - не знаю чому
hello_earth

4
+1: Просто натисніть на ситуацію, коли мені потрібен список змінних розмірів, щоб ініціалізуватися фіксованим набором нулів, перш ніж заповнити індекс (і додати додатки після цього, тому масив був непридатним). Ця відповідь отримує мій голос за те, що я практичний і простий.
Пройшов кодування

Казкова відповідь. @GoneCoding Мені було просто цікаво, чи внутрішньо знову ініціалізований список Lпросто повторно використає пам'ять string[10]як є (резервне зберігання списків також є масивом) чи він виділить нову власну пам'ять і потім скопіює вміст string[10]? string[10]отримає сміття, зібране автоматично, якщо Lвибере пізній маршрут.
RBT

3
@RBT Це єдиний недолік цього підходу: масив не буде повторно використаний, тож ви на мить матимете дві копії масиву (Список використовує масиви внутрішньо, як вам здається, що вам відомо). У рідкісних випадках це може бути проблемою, якщо у вас є величезна кількість елементів або обмежень пам'яті. І так, додатковий масив буде придатний для вивезення сміття, як тільки конструктор списку буде виконаний (інакше це було б жахливим витоком пам'яті). Зауважте, що придатність не означає "зібране одразу", а скоріше наступного разу, коли відбувається сміття.
AnorZaken

2
Ця відповідь виділяє 10 нових рядків - потім повторює копіювання їх за необхідністю. Якщо ви працюєте з великими масивами; тоді навіть не враховуйте це, оскільки для цього потрібно пам'ять удвічі, ніж прийнята відповідь.
UKMonkey

22

Використовуйте конструктор, який приймає int ("місткість") як аргумент:

List<string> = new List<string>(10);

РЕДАКТУ: Додам, що я погоджуюся з Фредеріком. Ви використовуєте Список таким чином, що в першу чергу суперечить усім міркуванням щодо його використання.

EDIT2:

EDIT 2: Те, що я зараз пишу, - це базовий клас, який пропонує функціонал за замовчуванням як частину більш широкої основи. У пропонованій мені програмі за замовчуванням розмір списку відомий розширено, тому я міг би використовувати масив. Однак я хочу запропонувати будь-якому базовому класу можливість його динамічно розширити, і тому я вибираю список.

Чому хтось повинен знати розмір списку з усіма нульовими значеннями? Якщо в списку немає реальних значень, я б очікував, що довжина дорівнює 0. Як би то не було, факт, що це невдача, свідчить про те, що це суперечить передбачуваному використанню класу.


46
Ця відповідь не виділяє 10 нульових записів у списку (що було вимогою), вона просто виділяє простір для 10 записів, перш ніж потрібно змінити розмір списку (тобто ємність), так що це не робить нічого іншого new List<string>(), що стосується проблеми . Молодці, набравши так багато голосів :)
Пройшло кодування

2
що перевантажений конструктор - це значення "початкової ємності", а не "розмір" або "довжина", і воно також не ініціалізує елементи
Метт Вілько,

Щоб відповісти "навіщо комусь це потрібно": мені зараз це потрібно для структур глибокого клонування дерева даних. Вузол може або не може заповнити своїх дітей, але мій базовий клас вузлів повинен мати можливість клонувати себе усіма його дочірніми вузлами. Бла-ха, це ще складніше. Але мені потрібно як заповнити мій ще порожній список, так list[index] = obj;і використовувати деякі інші можливості списку.
Bitterblue

3
@Bitterblue: Крім того, будь-яке попередньо виділене відображення, де у вас можуть бути не всі значення наперед. Безумовно, є використання; Цю відповідь я писав у свої менш досвідчені дні.
Ред С.

8

Створіть масив із кількістю потрібних елементів спочатку, а потім перетворіть масив у Список.

int[] fakeArray = new int[10];

List<int> list = fakeArray.ToList();

7

Чому ви використовуєте Список, якщо ви хочете ініціалізувати його з фіксованим значенням? Я можу зрозуміти, що - заради продуктивності - ви хочете надати йому початкову потужність, але чи не є однією з переваг списку над звичайним масивом, який він може зростати при необхідності?

Коли ви це зробите:

List<int> = new List<int>(100);

Ви створюєте список, місткість якого становить 100 цілих чисел. Це означає, що списку не потрібно буде "рости", поки ви не додасте 101-й елемент. Базовий масив списку буде ініціалізований довжиною 100.


1
"Чому ви використовуєте Список, якщо ви хочете ініціалізувати його з фіксованим значенням" Хороший момент.
Ред С.

7
Він попросив список, в якому кожен елемент був ініціалізований і список мав розмір, а не лише ємність. Ця відповідь невірна, як зараз.
Нік Фостер

Питання вимагає відповіді, а не критики на запитання. Іноді є причина зробити це так, на відміну від використання масиву. Якщо вас цікавить, чому це може бути так, можна задати питання щодо переповнення стека.
Діно Діні

5

Ініціалізація вмісту такого списку насправді не є списком. Списки призначені для розміщення предметів. Якщо ви хочете зіставити певні номери з певними об'єктами, розгляньте використання структури пари ключових значень, таких як хеш-таблиця або словник замість списку.


Це не дає відповіді на питання, а лише дає підставу його не задавати.
Діно Діні

5

Здається, ви наголошуєте на необхідності позиційної асоціації з вашими даними, тож чи не асоціативний масив був би більш придатним?

Dictionary<int, string> foo = new Dictionary<int, string>();
foo[2] = "string";

Це дає відповідь на інше запитання на запитання.
Діно Діні

4

Якщо ви хочете ініціалізувати список із N елементами певного фіксованого значення:

public List<T> InitList<T>(int count, T initValue)
{
  return Enumerable.Repeat(initValue, count).ToList();
}

Дивіться відповідь Джона Скіта про стурбованість щодо зміни розміру буфера за допомогою цієї методики.
Емі Б

1

Ви можете використовувати Linq для спритного ініціалізації списку зі значенням за замовчуванням. (Подібно до відповіді Девіда Б. )

var defaultStrings = (new int[10]).Select(x => "my value").ToList();

Перейдіть на один крок далі та ініціалізуйте кожен рядок з різними значеннями "рядок 1", "рядок 2", "рядок 3" тощо:

int x = 1;
var numberedStrings = (new int[10]).Select(x => "string " + x++).ToList();

1
string [] temp = new string[] {"1","2","3"};
List<string> temp2 = temp.ToList();

Як щодо List <string> temp2 = new List <string> (temp); Як і вже запропонований ОП.
Ред С.

Ед - цей насправді відповідає на питання - що не стосується потужності.
Боаз

Але в ОП вже заявили, що йому таке рішення не подобається.
Ред С.

1

У прийнятій відповіді (одна із зеленою галочкою) є питання.

Проблема:

var result = Lists.Repeated(new MyType(), sizeOfList);
// each item in the list references the same MyType() object
// if you edit item 1 in the list, you are also editing item 2 in the list

Рекомендую змінити рядок вище, щоб виконати копію об'єкта. Про це існує багато різних статей:

Якщо ви хочете ініціалізувати кожен елемент у своєму списку конструктором за замовчуванням, а не NULL, то додайте наступний метод:

public static List<T> RepeatedDefaultInstance<T>(int count)
    {
        List<T> ret = new List<T>(count);
        for (var i = 0; i < count; i++)
        {
            ret.Add((T)Activator.CreateInstance(typeof(T)));
        }
        return ret;
    }

1
Це не "питання" - це нормальна поведінка копіювання посилань. Не знаючи ситуації, ви не можете знати, чи підходить це копіювання об'єкта. Питання полягає не в тому, чи слід списки заповнювати копіями, - це про ініціалізацію списку з ємністю. Я поясню свою відповідь з точки зору поведінки він дійсно є, але я не думаю , що це розраховує як проблему в контексті цього питання.
Джон Скіт

@JonSkeet, я відповідав на статичний помічник, що нормально для примітивних типів, але не для еталонних. Сценарій, коли список ініціалізований з тим самим посилається елементом, не здається правильним. У тому сценарії, чому навіть є список, якщо кожен елемент у ньому вказує на один і той же об’єкт на купі.
Джеремі Рей Браун

Зразок сценарію: потрібно заповнити список рядків, спочатку із записом "Невідомо" для кожного елемента, потім ви зміните список для конкретних елементів. У цьому випадку цілком розумно, щоб усі значення "Невідомо" були посиланнями на один рядок. Було б безглуздо щоразу клонувати рядок. Наповнення рядка з кількома посиланнями на один і той же об'єкт не є "правильним" або "неправильним" у загальному розумінні. Поки читачі знають, що таке поведінка, вони вирішують, чи відповідає це їх конкретному випадку використання .
Джон Скіт

0

Повідомлення про IList: MSDN IList Зауваження : "Реалізації IList поділяються на три категорії: лише для читання, фіксованого розміру та змінного розміру. (...). Про загальну версію цього інтерфейсу див System.Collections.Generic.IList<T>."

IList<T>НЕ успадковує IList(але List<T>реалізує і те, IList<T>і інше IList), але завжди має змінний розмір . Оскільки .NET 4.5 у нас є, IReadOnlyList<T>окрім AFAIK, немає загального списку фіксованого розміру, який був би тим, що ви шукаєте.


0

Це зразок, який я використав для мого тестування на одиницю. Я створив список об’єкта класу. Тоді я використовував forloop, щоб додати «X» кількість об’єктів, яких я очікую від служби. Таким чином ви можете додати / ініціалізувати список для будь-якого заданого розміру.

public void TestMethod1()
    {
        var expected = new List<DotaViewer.Interface.DotaHero>();
        for (int i = 0; i < 22; i++)//You add empty initialization here
        {
            var temp = new DotaViewer.Interface.DotaHero();
            expected.Add(temp);
        }
        var nw = new DotaHeroCsvService();
        var items = nw.GetHero();

        CollectionAssert.AreEqual(expected,items);


    }

Сподіваюся, я допомогла вам, хлопці.


-1

Трохи запізнення, але перше запропоноване вами рішення здається мені набагато чистішим: ви не виділяєте пам'ять двічі. Конструктору Even List потрібно провести цикл через масив, щоб скопіювати його; він навіть не знає заздалегідь, що всередині є лише нульові елементи.

1. - виділити N - цикл N Вартість: 1 * виділити (N) + N * цикл_ітерації

2. - виділити N - виділити N + петлю () Вартість: 2 * виділити (N) + N * loop_iteration

Однак виділення списку циклів може бути швидшим, оскільки List - це вбудований клас, але C # - це компільований jit-sooo ...

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