Como inicializar um List <T> para um determinado tamanho (em oposição à capacidade)?

111

.NET oferece um contêiner de lista genérico cujo desempenho é quase idêntico (consulte a pergunta Desempenho de matrizes vs. listas). No entanto, eles são bastante diferentes na inicialização.

Os arrays são muito fáceis de inicializar com um valor padrão e, por definição, eles já têm um determinado tamanho:

string[] Ar = new string[10];

O que permite atribuir itens aleatórios com segurança, por exemplo:

Ar[5]="hello";

com lista as coisas são mais complicadas. Posso ver duas maneiras de fazer a mesma inicialização, nenhuma delas é o que você chamaria de elegante:

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

ou

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

Qual seria uma maneira mais limpa?

EDITAR: As respostas até agora referem-se à capacidade, que é algo mais do que preencher previamente uma lista. Por exemplo, em uma lista recém-criada com capacidade para 10, não se pode fazerL[2]="somevalue"

EDIT 2: As pessoas se perguntam por que eu quero usar listas dessa forma, já que não é a maneira que elas devem ser usadas. Posso ver duas razões:

  1. Alguém poderia argumentar de forma bastante convincente que as listas são os arrays da "próxima geração", adicionando flexibilidade quase sem penalidade. Portanto, deve-se usá-los por padrão. Estou apontando que eles podem não ser tão fáceis de inicializar.

  2. O que estou escrevendo atualmente é uma classe base que oferece funcionalidade padrão como parte de uma estrutura maior. Na funcionalidade padrão que ofereço, o tamanho da Lista é conhecido com antecedência e, portanto, eu poderia ter usado um array. No entanto, quero oferecer a qualquer classe base a chance de estendê-la dinamicamente e, portanto, opto por uma lista.

Boaz
fonte
1
"EDITAR: as respostas até agora referem-se à capacidade, que é algo mais do que preencher previamente uma lista. Por exemplo, em uma lista recém-criada com uma capacidade 10, não se pode fazer L [2] =" algum valor "" Dado isso modificação, talvez você deva reformular o título da pergunta ...
aranasaurus
Mas de que adianta preencher uma lista com valores vazios, porque é isso que o topicstarter está tentando fazer?
Frederik Gheysels
1
Se o mapeamento posicional for tão importante, não faria mais sentido usar um Dicionário <int, string>?
Greg D
7
Listnão é um substituto para Array. Eles resolvem problemas distintos. Se você deseja um tamanho fixo, deseja um Array. Se você usar um List, você está fazendo isso errado.
Zenexer
5
Sempre encontro respostas que tentam martelar argumentos como "Não vejo por que precisaria ..." de agravantes. Significa apenas isso: você não pode ver. Isso não significa necessariamente mais nada. Respeito o fato de as pessoas quererem sugerir melhores abordagens para um problema, mas deve ser formulado de forma mais humilde, por exemplo, "Tem certeza que precisa de uma lista? Talvez se você nos contasse mais sobre o seu problema ...". Dessa forma, torna-se agradável, envolvente e incentiva o OP a aprimorar sua pergunta. Seja um vencedor - seja humilde.
AnorZaken de

Respostas:

79

Não posso dizer que preciso disso com muita frequência - você poderia dar mais detalhes sobre por que deseja isso? Eu provavelmente o colocaria como um método estático em uma classe auxiliar:

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

Você poderia usar, Enumerable.Repeat(default(T), count).ToList()mas isso seria ineficiente devido ao redimensionamento do buffer.

Observe que se Tfor um tipo de referência, ele armazenará countcópias da referência passada para o valueparâmetro - portanto, todas farão referência ao mesmo objeto. Isso pode ou não ser o que você deseja, dependendo do seu caso de uso.

EDIT: Conforme observado nos comentários, você pode Repeatedusar um loop para preencher a lista, se quiser. Isso seria um pouco mais rápido também. Pessoalmente, acho o código Repeatmais descritivo e suspeito que no mundo real a diferença de desempenho seria irrelevante, mas sua milhagem pode variar.

Jon Skeet
fonte
1
Sei que este é um post antigo, mas estou curioso. Enumerable.Repeat tem preços muito piores em comparação com um loop for, de acordo com a última parte do link ( dotnetperls.com/initialize-array ). Além disso, AddRange () tem complexidade O (n) de acordo com o msdn. Não é um pouco contraproducente usar a solução fornecida em vez de um loop simples?
Jimmy
@Jimmy: Ambas as abordagens serão O (n), e acho que essa abordagem é mais descritiva do que estou tentando alcançar. Se você preferir um loop, fique à vontade para usá-lo.
Jon Skeet
@ Jimmy: Observe também que o benchmark está usando Enumerable.Repeat(...).ToArray(), que não é como estou usando.
Jon Skeet
1
Usei o Enumerable.Repeat()da seguinte maneira (implementado Paircomo o equivalente em C ++): Enumerable.Repeat( new Pair<int,int>(int.MaxValue,-1), costs.Count)percebendo o efeito colateral, que o Listestava cheio de cópias referenciadas para um único objeto. Mudar um elemento myList[i].First = 1mudou cada elemento no todo List. Levei horas para encontrar esse bug. Vocês conhecem alguma solução para esse problema (exceto apenas para usar um loop comum e usar .Add(new Pair...)?
00zetti
1
@ Pac0, acabei de adicionar uma resposta abaixo. As edições podem ser reprovadas.
Jeremy Ray Brown
136
List<string> L = new List<string> ( new string[10] );

fonte
3
pessoalmente, acho que essa é a maneira mais limpa - embora tenha sido mencionada no corpo da pergunta como potencialmente desajeitada - não sei por quê
hello_earth
4
+1: apenas atingiu uma situação onde eu precisava de uma lista de tamanho variável a ser inicializada com um conjunto fixo de nulos antes de preencher por meio de um índice (e adicionar extras depois, portanto, um array não era adequado). Essa resposta merece meu voto por ser prática e simples.
Gone Coding
Resposta fabulosa. @GoneCoding Eu estava pensando se internamente a lista recém-inicializada Lsimplesmente reutilizará a memória do estado em string[10]que se encontra (o armazenamento de backup de uma lista também é um array) ou alocará uma nova memória própria e, em seguida, copiará o conteúdo de string[10]? string[10]obterá lixo coletado automaticamente se Lselecionar a rota posterior.
RBT
3
@RBT Essa é a única desvantagem dessa abordagem: o array não será reutilizado, então você terá momentaneamente duas cópias do array (List usa arrays internamente como você parece estar ciente). Em casos raros, isso pode ser um problema se você tiver um grande número de elementos ou restrições de memória. E, sim, o array extra será elegível para a coleta de lixo assim que o construtor List for concluído (caso contrário, teria sido um terrível vazamento de memória). Observe que elegível não significa "coletado imediatamente", mas sim na próxima vez que a coleta de lixo for executada.
AnorZaken de
2
Essa resposta aloca 10 novas strings - então itera sobre elas, copiando-as conforme necessário. Se você estiver trabalhando com matrizes grandes; então nem mesmo considere isso, pois precisa do dobro de memória do que a resposta aceita.
UKMonkey
22

Use o construtor que leva um int ("capacidade") como argumento:

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

EDIT: Devo acrescentar que concordo com Frederik. Você está usando a Lista de uma forma que vai contra todo o raciocínio por trás de seu uso.

EDIT2:

EDIT 2: O que estou escrevendo atualmente é uma classe base que oferece funcionalidade padrão como parte de uma estrutura maior. Na funcionalidade padrão que ofereço, o tamanho da Lista é conhecido com antecedência e, portanto, eu poderia ter usado um array. No entanto, quero oferecer a qualquer classe base a chance de estendê-la dinamicamente e, portanto, opto por uma lista.

Por que alguém precisaria saber o tamanho de uma lista com todos os valores nulos? Se não houver valores reais na lista, eu esperaria que o comprimento fosse 0. De qualquer forma, o fato de ser desordenado demonstra que vai contra o uso pretendido da classe.

Ed S.
fonte
46
Esta resposta não aloca 10 entradas nulas na lista (que era o requisito), ela simplesmente aloca espaço para 10 entradas antes que um redimensionamento da lista seja necessário (ou seja, capacidade), então isso não faz nada diferente no new List<string>()que diz respeito ao problema . Parabéns por conseguir tantos votos positivos :)
Gone Coding
2
esse construtor sobrecarregado é o valor de "capacidade inicial", não o "tamanho" ou "comprimento", e também não inicializa os itens
Matt Wilko
Para responder "por que alguém precisaria disso": preciso disso agora para clonagem profunda de estruturas de dados de árvore. Um nó pode ou não preencher seus filhos, mas minha classe de nó base precisa ser capaz de se clonar com todos os seus nós filhos. Blah, blah é ainda mais complicado. Mas preciso preencher minha lista ainda vazia via list[index] = obj;e usar alguns outros recursos de lista.
Bitterblue
3
@Bitterblue: Além disso, qualquer tipo de mapeamento pré-alocado onde você pode não ter todos os valores iniciais. Certamente existem usos; Escrevi esta resposta em meus dias menos experientes.
Ed S.
8

Crie uma matriz com o número de itens que você deseja primeiro e, em seguida, converta a matriz em uma Lista.

int[] fakeArray = new int[10];

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

Por que você está usando uma lista se deseja inicializá-la com um valor fixo? Posso entender que, por uma questão de desempenho, você queira dar a ele uma capacidade inicial, mas uma das vantagens de uma lista em relação a um array regular não é que ela pode crescer quando necessário?

Quando você faz isso:

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

Você cria uma lista cuja capacidade é 100 inteiros. Isso significa que sua lista não precisará 'crescer' até que você adicione o 101º item. A matriz subjacente da lista será inicializada com um comprimento de 100.

Frederik Gheysels
fonte
1
"Por que você está usando uma lista se deseja inicializá-la com um valor fixo" Um bom ponto.
Ed S.
7
Ele pediu uma lista em que cada elemento fosse inicializado e a lista tivesse um tamanho, não apenas uma capacidade. Esta resposta está incorreta da forma como está agora.
Nic Foster
A pergunta requer uma resposta, não uma crítica a ser feita. Às vezes, há um motivo para fazer isso dessa maneira, em vez de usar uma matriz. Se estiver interessado em saber por que esse é o caso, pode-se fazer uma pergunta sobre o Stack Overflow.
Dino Dini
5

Inicializar o conteúdo de uma lista como essa não é realmente para o que as listas existem. As listas são projetadas para conter objetos. Se você deseja mapear números específicos para objetos específicos, considere o uso de uma estrutura de par de valores-chave, como uma tabela hash ou dicionário, em vez de uma lista.

Welbog
fonte
Isso não responde à pergunta, mas apenas dá uma razão para que ela não seja feita.
Dino Dini
5

Você parece estar enfatizando a necessidade de uma associação posicional com seus dados, então uma matriz associativa não seria mais adequada?

Dictionary<int, string> foo = new Dictionary<int, string>();
foo[2] = "string";
Greg D
fonte
Isso responde a uma pergunta diferente daquela que está sendo feita.
Dino Dini
4

Se você deseja inicializar a lista com N elementos de algum valor fixo:

public List<T> InitList<T>(int count, T initValue)
{
  return Enumerable.Repeat(initValue, count).ToList();
}
Amy B
fonte
Veja a resposta de John Skeet para preocupação com o redimensionamento do buffer por esta técnica.
Amy B de
1

Você pode usar o Linq para inicializar inteligentemente sua lista com um valor padrão. (Semelhante à resposta de David B. )

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

Vá um passo adiante e inicialize cada string com valores distintos "string 1", "string 2", "string 3", etc:

int x = 1;
var numberedStrings = (new int[10]).Select(x => "string " + x++).ToList();
James Lawruk
fonte
1
string [] temp = new string[] {"1","2","3"};
List<string> temp2 = temp.ToList();
Henk
fonte
Que tal List <string> temp2 = new List <string> (temp); Como o OP já sugerido.
Ed S.
Ed - este realmente responde à pergunta - que não é sobre capacidade.
Boaz
Mas o OP já afirmou que não gostou da solução.
Ed S.
1

A resposta aceita (aquela com a marca de seleção verde) tem um problema.

O problema:

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

Recomendo alterar a linha acima para realizar uma cópia do objeto. Existem muitos artigos diferentes sobre isso:

Se você deseja inicializar cada item da lista com o construtor padrão, em vez de NULL, adicione o seguinte método:

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;
    }
Jeremy Ray Brown
fonte
1
Não é um "problema" - é o comportamento normal de copiar referências. Sem saber a situação, você não pode saber se é adequado copiar o objeto. A questão não é se as listas devem ou não ser preenchidas com cópias - é sobre inicializar uma lista com uma capacidade. Esclarecerei minha resposta em termos do comportamento que ele tem, mas realmente não acho que isso conte como um problema no contexto desta pergunta.
Jon Skeet
@JonSkeet, eu estava respondendo ao auxiliar estático, que é adequado para tipos primitivos, mas não para tipos de referência. Um cenário onde uma lista é inicializada com o mesmo item referenciado não parece correto. Nesse cenário, por que ter a lista se todos os itens nela apontam para o mesmo objeto na pilha.
Jeremy Ray Brown
Cenário de amostra: você deseja preencher uma lista de strings, inicialmente com uma entrada "Desconhecida" para cada elemento e, em seguida, modificar a lista para elementos específicos. Nesse caso, é inteiramente razoável que todos os valores "Desconhecidos" sejam referências à mesma string. Seria inútil clonar a string todas as vezes. Preencher uma string com várias referências ao mesmo objeto não é "certo" ou "errado" em um sentido geral. Contanto que os leitores saibam qual é o comportamento, cabe a eles decidir se ele atende ao seu caso de uso específico .
Jon Skeet
0

Um aviso sobre IList: MSDN IList Observações : "As implementações IList se enquadram em três categorias: somente leitura, tamanho fixo e tamanho variável. (...). Para a versão genérica desta interface, consulte System.Collections.Generic.IList<T> ."

IList<T>NÃO herda de IList(mas List<T>implementa ambos IList<T>e IList), mas é sempre de tamanho variável . Desde o .NET 4.5, nós também temos o IReadOnlyList<T>AFAIK, não há uma lista genérica de tamanho fixo que seria o que você está procurando.

EricBDev
fonte
0

Este é um exemplo que usei para meu teste de unidade. Criei uma lista de objetos de classe. Então usei o forloop para adicionar o número 'X' de objetos que estou esperando do serviço. Desta forma, você pode adicionar / inicializar uma lista para qualquer tamanho determinado.

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


    }

Espero ter ajudado vocês.

Abhay Shiro
fonte
-1

Um pouco tarde, mas a primeira solução que você propôs parece muito mais limpa para mim: você não aloca memória duas vezes. Mesmo o construtor List precisa fazer um loop através do array para copiá-lo; nem mesmo sabe de antemão que há apenas elementos nulos dentro.

1. - alocar N - loop N Custo: 1 * alocar (N) + N * loop_iteration

2. - alocar N - alocar N + loop () Custo: 2 * alocar (N) + N * loop_iteration

No entanto, a alocação e os loops de List podem ser mais rápidos, já que List é uma classe integrada, mas C # é compilado por jit então ...

Victor Drouin
fonte