Как инициализировать List <T> заданным размером (в отличие от емкости)?

112

.NET предлагает общий контейнер списков, производительность которого практически идентична (см. Вопрос «Производительность массивов и списков»). Однако они совершенно разные по инициализации.

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

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);

Что было бы чище?

РЕДАКТИРОВАТЬ: ответы пока относятся к емкости, которая представляет собой нечто иное, чем предварительное заполнение списка. Например, в только что созданном списке с емкостью 10 нельзя сделатьL[2]="somevalue"

РЕДАКТИРОВАТЬ 2: Люди задаются вопросом, почему я хочу использовать списки таким образом, поскольку они не предназначены для использования. Я вижу две причины:

  1. Можно довольно убедительно утверждать, что списки - это массивы «следующего поколения», добавляющие гибкости практически без потерь. Поэтому их следует использовать по умолчанию. Я указываю, что их может быть не так просто инициализировать.

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

Вооз
источник
1
"РЕДАКТИРОВАТЬ: ответы на данный момент относятся к емкости, которая представляет собой нечто иное, чем предварительное заполнение списка. Например, в списке, только что созданном с емкостью 10, нельзя сделать L [2] =" somevalue "" Учитывая это модификация, возможно, вам следует перефразировать заголовок вопроса ...
aranasaurus
Но какой смысл в предварительном заполнении списка пустыми значениями, ведь это то, что пытается сделать автор темы?
Frederik Gheysels
1
Если позиционное сопоставление так важно, не имеет ли смысл использовать Dictionary <int, string>?
Greg D
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параметра, поэтому все они будут ссылаться на один и тот же объект. Это может быть или не быть тем, что вы хотите, в зависимости от вашего варианта использования.

РЕДАКТИРОВАТЬ: как отмечено в комментариях, вы можете Repeatedиспользовать цикл для заполнения списка, если хотите. Это тоже было бы немного быстрее. Лично я считаю, что код Repeatболее нагляден, и подозреваю, что в реальном мире разница в производительности не имеет значения, но ваш опыт может отличаться.

Джон Скит
источник
1
Я понимаю, что это старый пост, но мне любопытно. Enumerable.Repeat работает намного хуже по сравнению с циклом for в соответствии с последней частью ссылки ( dotnetperls.com/initialize-array ). Также AddRange () имеет сложность O (n) согласно msdn. Разве использование данного решения вместо простого цикла не контрпродуктивно?
Джимми
@ Джимми: Оба подхода будут O (n), и я считаю, что этот подход более информативен в отношении того, чего я пытаюсь достичь. Если вы предпочитаете петлю, не стесняйтесь ее использовать.
Джон Скит
@ Джимми: Также обратите внимание, что там используется тест 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, я только что добавил ответ ниже. Правки могут быть отклонены.
Джереми Рэй Браун
137
List<string> L = new List<string> ( new string[10] );

источник
3
лично я считаю, что это самый чистый способ - хотя он был упомянут в теле вопроса как потенциально неуклюжий - не знаю почему
hello_earth
4
+1: Просто попадите в ситуацию, когда мне нужно было инициализировать список переменного размера с фиксированным набором нулей перед заполнением через индекс (и добавлением дополнительных функций после этого, поэтому массив был неподходящим). Этот ответ получил мою оценку как практичный и простой.
Gone Coding
Сказочный ответ. @GoneCoding Мне просто было интересно, будет ли внутри вновь инициализированный список Lпросто повторно использовать память string[10]как есть (резервное хранилище списков также является массивом) или он будет выделять новую собственную память, а затем копировать содержимое string[10]? string[10]автоматически соберет мусор, если Lвыберет более поздний маршрут.
RBT
3
@RBT Это единственный недостаток этого подхода: массив не будет использоваться повторно, поэтому на мгновение у вас будет две копии массива (List использует массивы внутри, как вы, кажется, знаете). В редких случаях это может стать проблемой, если у вас огромное количество элементов или ограничения памяти. И да, дополнительный массив будет иметь право на сборку мусора, как только конструктор List будет готов (в противном случае это была бы ужасная утечка памяти). Обратите внимание, что «право» не означает «собирать сразу», а скорее при следующем запуске сборки мусора.
AnorZaken
2
Этот ответ выделяет 10 новых строк, а затем перебирает их, копируя их по мере необходимости. Если вы работаете с большими массивами; тогда даже не рассматривайте это, поскольку для этого требуется вдвое больше памяти, чем принятый ответ.
UKMonkey
22

Используйте конструктор, который принимает в качестве аргумента int ("емкость"):

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

РЕДАКТИРОВАТЬ: Я должен добавить, что согласен с Фредериком. Вы используете Список таким образом, который, в первую очередь, идет вразрез со всей логикой его использования.

РЕДАКТИРОВАТЬ2:

РЕДАКТИРОВАТЬ 2: То, что я сейчас пишу, является базовым классом, предлагающим функциональность по умолчанию как часть более крупной структуры. В предлагаемой мной функциональности по умолчанию размер списка известен заранее, поэтому я мог бы использовать массив. Однако я хочу предложить любому базовому классу возможность динамически расширять его, поэтому я выбираю список.

Зачем кому-то нужно знать размер списка со всеми нулевыми значениями? Если в списке нет реальных значений, я ожидаю, что длина будет равна 0. В любом случае, тот факт, что это неаккуратно, демонстрирует, что это идет вразрез с предполагаемым использованием класса.

Эд С.
источник
46
Этот ответ не выделяет 10 пустых записей в списке (что было требованием), он просто выделяет место для 10 записей до того, как потребуется изменение размера списка (т.е. емкость), поэтому это не делает ничего другого, new List<string>()чем проблема. . Молодец, получив столько голосов за :)
Gone Coding
2
этот перегруженный конструктор является значением «начальной емкости», а не «размером» или «длиной», и он также не инициализирует элементы
Мэтт Вилко,
Чтобы ответить «зачем кому-то это нужно»: мне это нужно прямо сейчас для глубокого клонирования древовидных структур данных. Узел может или не может заполнять своих дочерних узлов, но мой базовый класс узла должен иметь возможность клонировать себя со всеми его дочерними узлами. Мля-мля все еще сложнее. Но мне нужно как заполнить мой все еще пустой список, так list[index] = obj;и использовать некоторые другие возможности списка.
Bitterblue
3
@Bitterblue: Кроме того, любое заранее выделенное сопоставление, при котором у вас может не быть всех значений заранее. Конечно, есть применения; Я написал этот ответ в менее опытные дни.
Эд С.
8

Сначала создайте массив с нужным количеством элементов, а затем преобразуйте его в список.

int[] fakeArray = new int[10];

List<int> list = fakeArray.ToList();
mini998
источник
7

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

Когда вы это сделаете:

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

Вы создаете список, содержащий 100 целых чисел. Это означает, что вашему списку не нужно будет «расти», пока вы не добавите 101-й элемент. Базовый массив списка будет инициализирован длиной 100.

Фредерик Гейзель
источник
1
«Почему вы используете список, если хотите инициализировать его фиксированным значением». Хороший момент.
Эд С.
7
Он попросил список, в котором каждый элемент был инициализирован, и список имел размер, а не только емкость. Этот ответ неверен в его нынешнем виде.
Ник Фостер
Вопрос требует ответа, а не критики. Иногда есть причина сделать это таким образом, а не использовать массив. Если интересно, почему это может быть так, можно задать вопрос о переполнении стека.
Дино Дини
5

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

Велбог
источник
1
Это не отвечает на вопрос, а просто дает повод не задаваться.
Дино Дини,
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();
}
Эми Б
источник
См. Ответ Джона Скита по поводу изменения размера буфера с помощью этой техники.
Amy B
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); Как уже было предложено OP.
Эд С.
Эд - это действительно ответ на вопрос, который не о вместимости.
Boaz
Но ОП уже заявил, что это решение ему не нравится.
Эд С.
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, нет общего списка фиксированного размера, который был бы тем, что вы ищете.

EricBDev
источник
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

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

1. - выделить N - цикл N Стоимость: 1 * выделить (N) + N * loop_iteration

2. - выделить N - выделить N + loop () Стоимость: 2 * выделить (N) + N * loop_iteration

Однако распределение циклов в List может быть быстрее, так как List является встроенным классом, но C # скомпилирован jit sooo ...

Виктор Друэн
источник