Matrice contro elenco <T>: quando utilizzare quale?


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

Quali sono gli scenari in cui uno è preferibile all'altro? E perché?


9
Le matrici sono piuttosto obsolete, come visto in una discussione popolare qui. Anche sottolineato qui , e dal nostro host nel blog .
Gimel,

9
Se non sbaglio l'Elenco <> ha un array come struttura interna. Ogni volta che l'array interno viene riempito, copia semplicemente il contenuto in un array che è il doppio delle dimensioni (o qualche altra costante costante rispetto alle dimensioni correnti). en.wikipedia.org/wiki/Dynamic_array
Ykok

Ykok: Quello che dici sembra giusto, ho trovato qui il codice sorgente di List <> .
Carol,

19
@gimel Sostenere che le matrici sono obsolete è forse un po 'audace
awdz9nld

Risposte:


580

È raro, in realtà, che tu voglia usare un array. Sicuramente utilizzare List<T>ogni volta che si desidera aggiungere / rimuovere dati, poiché il ridimensionamento delle matrici è costoso. Se sai che i dati hanno una lunghezza fissa e vuoi micro-ottimizzare per qualche motivo molto specifico (dopo il benchmarking), allora un array può essere utile.

List<T>offre molte più funzionalità di un array (anche se LINQ ne migliora un po ') ed è quasi sempre la scelta giusta. Tranne gli paramsargomenti, ovviamente. ;-P

Come contatore - List<T>è monodimensionale; dove-come avete matrici rettangolari (ecc.) come int[,]o string[,,]- ma ci sono altri modi per modellare tali dati (se necessario) in un modello a oggetti.

Guarda anche:

Detto questo, uso molto gli array nel mio progetto protobuf-net ; interamente per prestazioni:

  • fa un sacco di bit-shifting, quindi byte[]è praticamente essenziale per la codifica;
  • Uso un byte[]buffer di rotolamento locale che riempio prima di inviarlo al flusso sottostante (e vv); più veloce di BufferedStreamecc .;
  • utilizza internamente un modello di oggetti basato su array ( Foo[]piuttosto che List<Foo>), poiché la dimensione viene fissata una volta creata e deve essere molto veloce.

Ma questa è sicuramente un'eccezione; per l'elaborazione generale della linea di business, List<T>ogni volta vince.


8
L'argomento sul ridimensionamento è totalmente valido. Tuttavia, le persone preferiscono le liste anche quando non è necessario ridimensionare. Per quest'ultimo caso, esiste un argomento solido e logico o non è altro che "le matrici sono fuori moda"?
Frederick The Fool,

6
"Sicuramente utilizzare un Elenco <T> ogni volta che si desidera aggiungere / rimuovere dati, poiché il ridimensionamento delle matrici è costoso." Elenco <T> utilizza un array internamente. Stavi pensando a LinkedList <T>?
dan-gph

14
Altre funzionalità == più complesse == non valide, a meno che non siano necessarie tali funzionalità. Questa risposta in sostanza elenca i motivi per cui gli array sono migliori, ma trae la conclusione opposta.
Eamon Nerbonne,

12
@EamonNerbonne se non stai usando quelle funzionalità, posso praticamente garantire che non ti faranno del male ... ma: il numero di raccolte che non hanno mai bisogno di una mutazione è molto più piccolo, nella mia esperienza, rispetto a quelli che lo sono mutato
Marc Gravell

7
@MarcGravell: dipende dal tuo stile di codifica. Nella mia esperienza praticamente nessuna collezione è mai stata mutata. Questo è; le raccolte vengono recuperate dal database o costruite da una fonte, ma l'ulteriore elaborazione viene sempre eseguita ricreando una nuova raccolta (ad es. mappa / filtro, ecc.). Anche dove è necessaria una mutazione concettuale, tende a essere più semplice generare una nuova raccolta. Ho sempre mutato una raccolta come ottimizzazione delle prestazioni e tali ottimizzazioni tendono ad essere altamente locali e non esporre la mutazione ai consumatori API.
Eamon Nerbonne,

121

In realtà non ho ancora detto di aggiungere un link che mi sorprende: il post sul blog di Eric Lippert su "Le matrici sono state considerate piuttosto dannose".

Puoi giudicare dal titolo che suggerisce di usare le collezioni ovunque sia pratico, ma come giustamente sottolinea Marc, ci sono molti posti in cui un array è davvero l'unica soluzione pratica.


2
Finalmente sono riuscito a leggere questo oltre 3 anni dopo haha. Buon articolo allora, buon articolo ora. :)
Spencer Ruport,

21

Nonostante le altre risposte consigliate List<T>, ti consigliamo di utilizzare le matrici durante la gestione:

  • dati bitmap immagine
  • altre strutture di dati di basso livello (ovvero protocolli di rete)

1
Perché per i protocolli di rete? Non preferiresti utilizzare strutture personalizzate qui e dare loro un serializzatore speciale o un layout di memoria esplicito? Inoltre, cosa parla contro l'utilizzo List<T>di un array qui anziché di un byte?
Konrad Rudolph,

8
@Konrad - beh, per cominciare, Stream.Read e Stream.Write funzionano con byte [], così come Codifica ecc ...
Marc Gravell

12

A meno che non ti interessi davvero delle prestazioni, e con questo intendo "Perché stai usando .Net invece di C ++?" dovresti rimanere con Elenco <>. È più facile da mantenere e fa tutto il lavoro sporco di ridimensionare un array dietro le quinte per te. (Se necessario, Elenco <> è piuttosto intelligente nella scelta delle dimensioni dell'array, quindi non è necessario in genere.)


15
"Perché stai usando .Net invece di C ++?" XNA
Bengt,

6

Le matrici dovrebbero essere usate in preferenza all'Elenco quando l'immutabilità della raccolta stessa fa parte del contratto tra il codice cliente e fornitore (non necessariamente l'immutabilità degli articoli all'interno della raccolta) E quando IEnumerable non è adatto.

Per esempio,

var str = "This is a string";
var strChars = str.ToCharArray();  // returns array

È chiaro che la modifica di "strChars" non muta l'oggetto originale "str", indipendentemente dalla conoscenza a livello di implementazione del tipo sottostante di "str".

Ma supponiamo che

var str = "This is a string";
var strChars = str.ToCharList();  // returns List<char>
strChars.Insert(0, 'X');

In questo caso, non è chiaro solo dallo snippet di codice se il metodo insert inserirà o non muterà l'oggetto "str" ​​originale. Richiede una conoscenza a livello di implementazione di String per fare quella determinazione, che rompe l'approccio Design by Contract. Nel caso di String, non è un grosso problema, ma può essere un grosso problema in quasi tutti gli altri casi. L'impostazione dell'elenco su sola lettura aiuta ma provoca errori di runtime, non di compilazione.


Sono relativamente nuovo in C # ma non mi è chiaro perché la restituzione di un elenco suggerirebbe la mutabilità dei dati originali in un modo in cui la restituzione di un array non lo farebbe. Avrei comunque pensato che un metodo il cui nome inizia con Toquello di creare un oggetto che non ha la possibilità di modificare l'istanza originale, al contrario del strChars as char[]quale se valido suggerirebbe che ora è possibile modificare l'oggetto originale.
Tim MB

@TimMB C'è l'immutabilità della raccolta (impossibile aggiungere o elementi remoti) e l'immutabilità degli oggetti nella raccolta. Mi riferivo a quest'ultimo, mentre probabilmente stai combinando i due. La restituzione di un array assicura al client che non è possibile aggiungere / rimuovere elementi. In tal caso, alloca nuovamente l'array e si assicura che non influirà sull'originale. Restituendo un elenco, tali garanzie non vengono fatte e l'originale potrebbe essere interessato (dipende dall'implementazione). La modifica di elementi all'interno della raccolta (sia array che elenco) potrebbe influire sull'originale, se il tipo di elemento non è una struttura.
Herman Schoenfeld il

Grazie per il chiarimento. Sono ancora confuso (probabilmente perché vengo dal mondo C ++). Se strutilizza internamente un array e ToCharArrayrestituisce un riferimento a questo array, il client può mutare strmodificando gli elementi di tale array, anche se la dimensione rimane fissa. Eppure scrivi 'È chiaro che la modifica di "strChars" non muterà l'oggetto "str" ​​originale'. Cosa mi sto perdendo qui? Da quello che posso vedere, in entrambi i casi il cliente potrebbe avere accesso alla rappresentazione interna e, indipendentemente dal tipo, ciò consentirebbe una mutazione di qualche tipo.
Tim MB

3

Se io so esattamente quanti elementi ho intenzione di necessità, dire che ho bisogno 5 elementi e solo mai 5 elementi allora io uso un array. Altrimenti uso solo un elenco <T>.


1
Perché non dovresti usare un Elenco <T> nel caso in cui conosci il numero di elementi?
Oliver,

3

Il più delle volte, usare a Listsarebbe sufficiente. A Listutilizza un array interno per gestire i suoi dati e ridimensiona automaticamente l'array quando aggiunge più elementi Listrispetto alla sua capacità corrente, il che lo rende più facile da usare rispetto a un array, dove è necessario conoscere in anticipo la capacità.

Vedere http://msdn.microsoft.com/en-us/library/ms379570(v=vs.80).aspx#datastructures20_1_topic5 per ulteriori informazioni sugli elenchi in C # o semplicemente decompilare System.Collections.Generic.List<T>.

Se hai bisogno di dati multidimensionali (ad esempio usando una matrice o nella programmazione grafica), probabilmente andresti con un arrayinvece.

Come sempre, se la memoria o le prestazioni sono un problema, misuralo! Altrimenti potresti fare false ipotesi sul codice.


1
Ciao, potresti spiegare perché "Il tempo di ricerca di un elenco sarebbe O (n)" è vero? Per quanto ne so, List <T> utilizza array dietro le quinte.
libellula,

1
@dragonfly hai perfettamente ragione. Fonte . All'epoca, supponevo che l'implementazione usasse i puntatori, ma da allora ho imparato diversamente. Dal link sopra: 'Il recupero del valore di questa proprietà è un'operazione O (1); l'impostazione della proprietà è anche un'operazione O (1). '
Sune Rievers,

2

Array vs. Gli elenchi rappresentano un classico problema di manutenibilità e prestazioni. La regola empirica che quasi tutti gli sviluppatori seguono è che dovresti sparare per entrambi, ma quando entrano in conflitto, scegli la manutenibilità piuttosto che le prestazioni. L'eccezione a tale regola è quando le prestazioni hanno già dimostrato di essere un problema. Se porti questo principio ad Arrays vs. Elenchi, quindi quello che ottieni è questo:

Utilizzare elenchi fortemente tipizzati fino a quando non si verificano problemi di prestazioni. Se si riscontra un problema di prestazioni, prendere una decisione se abbandonare gli array andrà a beneficio della soluzione con prestazioni più di quanto non pregiudichi la soluzione in termini di manutenzione.


1

Un'altra situazione non ancora menzionata è quando si avrà un gran numero di elementi, ognuno dei quali è costituito da un insieme fisso di variabili correlate ma indipendenti unite (ad esempio le coordinate di un punto o i vertici di un triangolo 3d). Una serie di strutture a campo esposto permetterà ai suoi elementi di essere efficacemente modificati "sul posto", cosa che non è possibile con nessun altro tipo di raccolta. Poiché una matrice di strutture mantiene i suoi elementi consecutivamente nella RAM, gli accessi sequenziali agli elementi della matrice possono essere molto veloci. In situazioni in cui il codice dovrà effettuare molti passaggi sequenziali attraverso un array, un array di strutture può sovraperformare un array o altra raccolta di riferimenti a oggetti di classe di un fattore 2: 1; ulteriore,

Sebbene le matrici non siano ridimensionabili, non è difficile che il codice memorizzi un riferimento di matrice insieme al numero di elementi in uso e sostituisca la matrice con una più grande come richiesto. In alternativa, si potrebbe facilmente scrivere codice per un tipo che si è comportato in modo molto simile a un List<T>ma ha esposto il suo archivio di supporto, consentendo così di dire uno MyPoints.Add(nextPoint);o MyPoints.Items[23].X += 5;. Si noti che quest'ultimo non costituirebbe necessariamente un'eccezione se il codice tentasse di accedere oltre la fine dell'elenco, ma l'utilizzo sarebbe altrimenti concettualmente abbastanza simile a List<T>.


Quello che hai descritto è un Elenco <>. C'è un indicizzatore in modo da poter accedere direttamente all'array sottostante e l'Elenco <> manterrà le dimensioni per te.
Carl,

@Carl: dato ad esempio Point[] arr;, è possibile dire il codice, ad es arr[3].x+=q;. Usando ad esempio List<Point> list, sarebbe invece necessario dire Point temp=list[3]; temp.x+=q; list[3]=temp;. Sarebbe utile se List<T>avesse un metodo Update<TP>(int index, ActionByRefRef<T,TP> proc, ref TP params). e i compilatori potrebbero trasformarsi list[3].x+=q;in {list.Update(3, (ref int value, ref int param)=>value+=param, ref q);ma tale funzionalità non esiste.
Supercat,

Buone notizie. Funziona. list[0].X += 3;aggiungerà 3 alla proprietà X del primo elemento dell'elenco. Ed listè una List<Point>ed Pointè una classe con proprietà X e Y
Carl,

1

Gli elenchi in .NET sono wrapper su array e usano un array internamente. La complessità temporale delle operazioni sugli elenchi è la stessa di quella degli array, tuttavia c'è un po 'più di sovraccarico con tutta la funzionalità / facilità d'uso degli elenchi (come il ridimensionamento automatico e i metodi forniti con la classe list). Praticamente, consiglierei di utilizzare gli elenchi in tutti i casi, a meno che non ci sia un motivo convincente per non farlo, ad esempio se è necessario scrivere un codice estremamente ottimizzato o lavorare con un altro codice costruito attorno agli array.


0

Piuttosto che passare attraverso un confronto delle caratteristiche di ciascun tipo di dati, penso che la risposta più pragmatica sia "le differenze probabilmente non sono così importanti per quello che devi realizzare, specialmente perché entrambi implementano IEnumerable, quindi segui le convenzioni popolari e usa un Listfino a quando non avrai un motivo per non farlo, a quel punto probabilmente avrai il tuo motivo per usare un array su un List".

La maggior parte delle volte nel codice gestito vorrai preferire che le raccolte siano il più facili da lavorare rispetto a preoccuparti delle micro-ottimizzazioni.


0

Potrebbero essere impopolari, ma io sono un fan di Array nei progetti di gioco. - La velocità di iterazione può essere importante in alcuni casi, foreach su un array ha un sovraccarico significativamente inferiore se non stai facendo molto per elemento - Aggiungere e rimuovere non è così difficile con le funzioni di supporto - È più lento, ma nei casi in cui lo costruisci solo una volta potrebbe non importare - Nella maggior parte dei casi, si spreca meno memoria extra (davvero significativo solo con le matrici di strutture) - Leggermente meno spazzatura e puntatori e inseguimento dei puntatori

Detto questo, uso List molto più spesso degli array in pratica, ma ognuno di essi ha il suo posto.

Sarebbe bello se List fosse un tipo incorporato in modo che potessero ottimizzare il sovraccarico del wrapper e dell'enumerazione.


0

Il popolamento di un elenco è più semplice di un array. Per gli array, è necessario conoscere la lunghezza esatta dei dati, ma per gli elenchi, la dimensione dei dati può essere qualsiasi. Inoltre, puoi convertire un elenco in un array.

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

Poiché nessuno menziona: in C #, un array è un elenco. MyClass[]ed List<MyClass>entrambi implementano IList<MyClass>. (es. void Foo(IList<int> foo)può essere chiamato come Foo(new[] { 1, 2, 3 })o Foo(new List<int> { 1, 2, 3 }))

Quindi, se stai scrivendo un metodo che accetta un List<MyClass>come argomento, ma utilizza solo un sottoinsieme di funzionalità, potresti voler dichiararlo come IList<MyClass>invece per comodità dei chiamanti.

Dettagli:


"In C #, un array è un elenco" Questo non è vero; un array non è un List, implementa solo l' IListinterfaccia.
Rufus L

-1

Dipende completamente dai contesti in cui è necessaria la struttura dei dati. Ad esempio, se si stanno creando elementi da utilizzare per altre funzioni o servizi utilizzando Elenco è il modo perfetto per realizzarlo.

Ora, se si dispone di un elenco di elementi e si desidera solo visualizzarli, dire su un array di pagine Web è il contenitore che è necessario utilizzare.


1
Se disponi di un elenco di elementi e desideri semplicemente visualizzarli, che cosa c'è di sbagliato nell'utilizzare l'elenco che hai già? Cosa offrirebbe un array qui?
Marc Gravell

1
E per "creare elementi da utilizzare con altre funzioni o servizi", in realtà, preferirei un blocco iteratore con IEnumerable<T>- quindi posso eseguire lo streaming di oggetti piuttosto che memorizzarli in buffer.
Marc Gravell

-1

Vale la pena menzionare la capacità di fare casting in loco.

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

Quindi Array offre un po 'più di flessibilità quando viene utilizzato nel tipo restituito o nell'argomento per le funzioni.

IWork[] GetAllWorks( )
{
    List<Foo> fooWorks = new List<Foo>( );
    return fooWorks.ToArray( ); // Fine
}

void ExecuteWorks( IWork[] works ) { } // Also accept Foo[]

Si noti che la varianza dell'array è interrotta .
McCarton,
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.