Come inizializzare un List <T> a una data dimensione (in contrapposizione alla capacità)?


111

.NET offre un contenitore di elenchi generico le cui prestazioni sono quasi identiche (vedere la domanda sulle prestazioni di array e sugli elenchi). Tuttavia sono abbastanza diversi nell'inizializzazione.

Gli array sono molto facili da inizializzare con un valore predefinito e per definizione hanno già una certa dimensione:

string[] Ar = new string[10];

Ciò consente di assegnare in modo sicuro elementi casuali, ad esempio:

Ar[5]="hello";

con l'elenco le cose sono più complicate. Posso vedere due modi per eseguire la stessa inizializzazione, nessuno dei quali è ciò che chiameresti elegante:

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

o

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

Quale sarebbe un modo più pulito?

EDIT: Le risposte finora si riferiscono alla capacità, che è qualcos'altro rispetto al pre-compilare un elenco. Ad esempio, su un elenco appena creato con una capacità di 10, non si può fareL[2]="somevalue"

EDIT 2: le persone si chiedono perché voglio usare gli elenchi in questo modo, poiché non è il modo in cui sono destinati ad essere utilizzati. Posso vedere due ragioni:

  1. Si potrebbe sostenere in modo abbastanza convincente che gli elenchi sono gli array di "nuova generazione", aggiungendo flessibilità quasi senza penalità. Pertanto si dovrebbero usarli per impostazione predefinita. Sto facendo notare che potrebbero non essere così facili da inizializzare.

  2. Quello che sto scrivendo attualmente è una classe base che offre funzionalità predefinite come parte di un framework più grande. Nella funzionalità predefinita che offro, la dimensione dell'elenco è nota in avanzato e quindi avrei potuto utilizzare un array. Tuttavia, voglio offrire a qualsiasi classe base la possibilità di estenderla dinamicamente e quindi opto per un elenco.


1
"EDIT: Le risposte finora si riferiscono alla capacità, che è qualcosa di diverso dal pre-compilare un elenco. Ad esempio, su un elenco appena creato con una capacità 10, non si può fare L [2] =" somevalue "" Dato questo modifica, forse dovresti riformulare il titolo della domanda ...
aranasaurus

Ma qual è lo scopo di precompilare un elenco con valori vuoti, perché è quello che il topicstarter sta cercando di fare?
Frederik Gheysels

1
Se la mappatura posizionale è così cruciale, non avrebbe più senso usare un Dictionary <int, string>?
Greg D,

7
Listnon è un sostituto di Array. Risolvono problemi nettamente separati. Se vuoi una dimensione fissa, vuoi un file Array. Se usi a List, lo stai facendo male.
Zenexer

5
Trovo sempre le risposte che cercano di martellare in argomenti come "Non riesco a vedere perché avrei mai bisogno di ..." aggravanti. Significa solo questo: non puoi vederlo. Non significa necessariamente nient'altro. Rispetto che le persone vogliano suggerire approcci migliori a un problema, ma dovrebbe essere formulato in modo più umile, ad esempio "Sei sicuro di aver bisogno di un elenco? Forse se ci dicessi di più sul tuo problema ...". In questo modo diventa piacevole, coinvolgente e incoraggia il PO a migliorare la propria domanda. Sii un vincitore - sii umile.
AnorZaken

Risposte:


79

Non posso dire di averne bisogno molto spesso - potresti fornire maggiori dettagli sul motivo per cui lo desideri? Probabilmente lo metterei come metodo statico in una classe helper:

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

Si potrebbe usareEnumerable.Repeat(default(T), count).ToList() , ma che sarebbe dovuto inefficiente di buffer ridimensionamento.

Nota che se Tè un tipo di riferimento, memorizzerà le countcopie del riferimento passato pervalue parametro, quindi faranno riferimento allo stesso oggetto. Potrebbe essere o meno quello che vuoi, a seconda del tuo caso d'uso.

MODIFICA: come indicato nei commenti, puoi Repeatedutilizzare un ciclo per popolare l'elenco, se lo desideri. Anche questo sarebbe leggermente più veloce. Personalmente trovo che il codice sia Repeatpiù descrittivo e sospetto che nel mondo reale la differenza di prestazioni sarebbe irrilevante, ma il tuo chilometraggio potrebbe variare.


1
Mi rendo conto che questo è un vecchio post, ma sono curioso. Enumerable.Repeat tariffe molto peggiori rispetto a un ciclo for, come per l'ultima parte del collegamento ( dotnetperls.com/initialize-array ). Anche AddRange () ha complessità O (n) come per msdn. Non è un po 'controproducente utilizzare la soluzione data invece di un semplice ciclo?
Jimmy

@ Jimmy: Entrambi gli approcci saranno O (n), e trovo che questo approccio sia più descrittivo di ciò che sto cercando di ottenere. Se preferisci un loop, sentiti libero di usarlo.
Jon Skeet

@ Jimmy: Nota anche che il benchmark sta usando Enumerable.Repeat(...).ToArray(), che non è come lo sto usando.
Jon Skeet

1
Ho usato Enumerable.Repeat()nel modo seguente (implementato Pairproprio come l'equivalente C ++): Enumerable.Repeat( new Pair<int,int>(int.MaxValue,-1), costs.Count)notando l'effetto collaterale, che Listera pieno di copie referenziate a un singolo oggetto. Cambiare un elemento come ha myList[i].First = 1cambiato ogni singolo elemento nel suo insieme List. Mi ci sono volute ore per trovare questo bug. Ragazzi, conoscete qualche soluzione a questo problema (tranne che per l'utilizzo di un loop comune e l'uso .Add(new Pair...)?
00zetti

1
@ Pac0, ho appena aggiunto una risposta di seguito. Le modifiche possono essere disapprovate.
Jeremy Ray Brown

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

3
personalmente penso che questo sia il modo più pulito - sebbene sia stato menzionato nel corpo della domanda come potenzialmente goffo - non so perché
hello_earth

4
+1: Basta colpire una situazione in cui avevo bisogno di un elenco di dimensioni variabili da inizializzare con un set fisso di null prima di popolarlo tramite un indice (e aggiungere extra in seguito, quindi un array non era adatto). Questa risposta ottiene il mio voto per essere pratica e semplice.
Gone Coding

Risposta favolosa. @GoneCoding Mi stavo solo chiedendo se internamente l'elenco appena inizializzato Lriutilizzerà semplicemente la memoria di così string[10]com'è (anche l'archivio di backup di un elenco è un array) o allocherà una nuova memoria e quindi copierà il contenuto di string[10]? string[10]otterrà automaticamente la raccolta dei rifiuti se Lseleziona il percorso successivo.
RBT

3
@RBT Questo è l'unico svantaggio di questo approccio: l'array non verrà riutilizzato, quindi avrai momentaneamente due copie dell'array (List usa gli array internamente come sembri essere a conoscenza). In rari casi questo potrebbe essere un problema se si dispone di un numero enorme di elementi o di vincoli di memoria. E sì, l'array extra sarà idoneo per la garbage collection non appena il costruttore List sarà terminato (altrimenti sarebbe stata un'orribile perdita di memoria). Tieni presente che idoneo non significa "raccolto subito", ma piuttosto la prossima volta che viene eseguita la garbage collection.
AnorZaken

2
Questa risposta alloca 10 nuove stringhe, quindi le itera copiandole come richiesto. Se lavori con array di grandi dimensioni; quindi non considerarlo nemmeno perché richiede il doppio della memoria rispetto alla risposta accettata.
UKMonkey

22

Usa il costruttore che accetta un int ("capacità") come argomento:

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

EDIT: dovrei aggiungere che sono d'accordo con Frederik. Stai usando la Lista in un modo che va contro l'intero ragionamento dietro al suo utilizzo in primo luogo.

EDIT2:

EDIT 2: Quello che sto scrivendo attualmente è una classe base che offre funzionalità predefinite come parte di un framework più grande. Nella funzionalità predefinita che offro, la dimensione dell'elenco è nota in avanzato e quindi avrei potuto utilizzare un array. Tuttavia, voglio offrire a qualsiasi classe base la possibilità di estenderla dinamicamente e quindi opto per un elenco.

Perché qualcuno dovrebbe conoscere la dimensione di un elenco con tutti i valori nulli? Se non ci sono valori reali nell'elenco, mi aspetto che la lunghezza sia 0. In ogni caso, il fatto che questo sia cludgy dimostra che va contro l'uso previsto della classe.


46
Questa risposta non alloca 10 voci nulle nell'elenco (che era il requisito), alloca semplicemente spazio per 10 voci prima che sia richiesto un ridimensionamento della lista (cioè capacità), quindi questo non fa nulla di diverso per new List<string>()quanto riguarda il problema . Complimenti per aver ottenuto così tanti voti positivi :)
Gone Coding

2
quel costruttore sovraccarico è il valore della "capacità iniziale" non la "dimensione" o la "lunghezza", e non inizializza neanche gli elementi
Matt Wilko

Per rispondere "perché qualcuno dovrebbe aver bisogno di questo": ne ho bisogno adesso per la clonazione profonda delle strutture di dati ad albero. Un nodo potrebbe o non potrebbe popolare i suoi figli, ma la mia classe del nodo base deve essere in grado di clonarsi con tutti i suoi nodi figlio. Blah, blah è ancora più complicato. Ma ho bisogno sia di popolare il mio elenco ancora vuoto tramite list[index] = obj;sia di utilizzare alcune altre funzionalità di elenco.
Bitterblue

3
@Bitterblue: Inoltre, qualsiasi tipo di mappatura pre-allocata in cui potresti non avere tutti i valori in anticipo. Ci sono certamente usi; Ho scritto questa risposta nei miei giorni meno esperti.
Ed S.

8

Crea prima un array con il numero di elementi che desideri, quindi converti l'array in un elenco.

int[] fakeArray = new int[10];

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

7

Perché stai usando una lista se vuoi inizializzarla con un valore fisso? Posso capire che -per il bene delle prestazioni- vuoi dargli una capacità iniziale, ma non è uno dei vantaggi di un elenco rispetto a un array normale che può crescere quando necessario?

Quando lo fai:

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

Si crea un elenco la cui capacità è di 100 numeri interi. Ciò significa che il tuo elenco non avrà bisogno di "crescere" finché non aggiungi il 101esimo elemento. L'array sottostante dell'elenco verrà inizializzato con una lunghezza di 100.


1
"Perché stai usando una lista se vuoi inizializzarla con un valore fisso" Un buon punto.
Ed S.

7
Ha chiesto un elenco in cui ogni elemento fosse inizializzato e l'elenco avesse una dimensione, non solo una capacità. Questa risposta non è corretta così com'è ora.
Nic Foster

La domanda richiede una risposta, non una critica da porre. A volte c'è un motivo per farlo in questo modo, invece di usare un array. Se sei interessato al motivo per cui questo potrebbe essere il caso, puoi fare una domanda di Stack Overflow per questo.
Dino Dini

5

Inizializzare il contenuto di un elenco in questo modo non è esattamente ciò a cui servono gli elenchi. Gli elenchi sono progettati per contenere oggetti. Se desideri mappare numeri particolari su oggetti particolari, considera l'utilizzo di una struttura di coppie chiave-valore come una tabella hash o un dizionario invece di un elenco.


Questo non risponde alla domanda, ma fornisce semplicemente un motivo per non essere chiesto.
Dino Dini

5

Sembra che tu stia enfatizzando la necessità di un'associazione di posizione con i tuoi dati, quindi un array associativo non sarebbe più adatto?

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

Questo sta rispondendo a una domanda diversa da quella che viene posta.
Dino Dini

4

Se vuoi inizializzare l'elenco con N elementi di qualche valore fisso:

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

Vedere la risposta di John Skeet per la preoccupazione sul ridimensionamento del buffer con questa tecnica.
Amy B

1

Puoi usare Linq per inizializzare in modo intelligente il tuo elenco con un valore predefinito. (Simile alla risposta di David B. )

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

Vai oltre e inizializza ogni stringa con valori distinti "stringa 1", "stringa 2", "stringa 3" e così via:

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

Che ne dici di List <string> temp2 = new List <string> (temp); Come l'OP già suggerito.
Ed S.

Ed - questo in realtà risponde alla domanda - che non riguarda la capacità.
Boaz

Ma l'OP ha già affermato che non gli piaceva questa soluzione.
Ed S.

1

La risposta accettata (quella con il segno di spunta verde) presenta un problema.

Il 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

Consiglio di cambiare la riga sopra per eseguire una copia dell'oggetto. Ci sono molti articoli diversi su questo:

Se desideri inizializzare ogni elemento nell'elenco con il costruttore predefinito, anziché NULL, aggiungi il seguente metodo:

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
Non è un "problema": è il normale comportamento di copiare i riferimenti. Senza conoscere la situazione, non puoi sapere se è opportuno copiare l'oggetto. La domanda non è se gli elenchi debbano essere popolati o meno con copie, ma si tratta di inizializzare un elenco con una capacità. Io chiarire la mia risposta in termini di comportamento che non ha, ma io in realtà non credo che conta come un problema nel contesto di questa domanda.
Jon Skeet

@ JonSkeet, stavo rispondendo all'helper statico, che va bene per i tipi primitivi ma non per i tipi di riferimento. Uno scenario in cui un elenco viene inizializzato con lo stesso elemento di riferimento non sembra corretto. In quello scenario perché anche avere l'elenco se ogni elemento in esso punta allo stesso oggetto nell'heap.
Jeremy Ray Brown

Scenario di esempio: si desidera popolare un elenco di stringhe, inizialmente con una voce "Sconosciuto" per ogni elemento, quindi si modifica l'elenco per elementi specifici. In tal caso è del tutto ragionevole che tutti i valori "Sconosciuto" siano riferimenti alla stessa stringa. Sarebbe inutile clonare la stringa ogni volta. Il popolamento di una stringa con più riferimenti allo stesso oggetto non è "giusto" o "sbagliato" in senso generale. Finché i lettori sanno qual è il comportamento, spetta a loro decidere se soddisfa il loro caso d'uso specifico .
Jon Skeet

0

Avviso su IList: MSDN IList Osservazioni : "Le implementazioni di IList rientrano in tre categorie: sola lettura, dimensione fissa e dimensione variabile. (...). Per la versione generica di questa interfaccia, vedere System.Collections.Generic.IList<T>."

IList<T>NON eredita da IList(maList<T> implementa sia IList<T>e IList), ma è sempre di dimensione variabile . A partire da .NET 4.5, abbiamo anche, IReadOnlyList<T>ma per quanto ne so, non esiste un elenco generico di dimensioni fisse che sarebbe quello che stai cercando.


0

Questo è un campione che ho usato per il mio unit test. Ho creato un elenco di oggetti di classe. Quindi ho usato forloop per aggiungere il numero "X" di oggetti che mi aspetto dal servizio. In questo modo puoi aggiungere / inizializzare un elenco per qualsiasi dimensione data.

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


    }

Spero di esservi stato d'aiuto ragazzi.


-1

Un po 'tardi ma la prima soluzione che mi hai proposto mi sembra molto più pulita: non allocare la memoria due volte. Anche List constrcutor ha bisogno di scorrere l'array per copiarlo; non sa nemmeno in anticipo che ci sono solo elementi nulli all'interno.

1. - alloca N - loop N Costo: 1 * alloca (N) + N * loop_iteration

2. - alloca N - alloca N + loop () Costo: 2 * alloca (N) + N * loop_iteration

Tuttavia l'allocazione di un ciclo di List potrebbe essere più veloce poiché List è una classe incorporata, ma C # è compilato in modo jit ...

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.