2012-02-07 7 views
23

Da quello che ho letto, è stata presa una decisione di progettazione per determinati tipi di enumeratori delle raccolte in modo da essere strutture modificabili anziché tipi di riferimento per motivi di prestazioni. List.Enumerator è il più noto.Perché le matrici C# utilizzano un tipo di riferimento per l'enumerazione, ma l'elenco <T> utilizza una struttura mutabile?

Stavo indagando su un vecchio codice che utilizzava gli array e sono stato sorpreso di scoprire che gli array C# restituiscono il tipo SZGenericArrayEnumerator come tipo di enumeratore generico, che è un tipo di riferimento.

Mi chiedo se qualcuno sa perché l'iteratore generico di Array è stato implementato come un tipo di riferimento quando molte altre raccolte di prestazioni critiche utilizzavano invece strutture mutevoli.

risposta

36

Da quello che ho letto, è stata presa una decisione di progettazione per determinati tipi di enumeratore di raccolte per essere strutture modificabili anziché tipi di riferimento per motivi di prestazioni.

Buona domanda.

Prima di tutto, hai ragione. Anche se, in generale, i tipi di valore mutabili sono di cattivo odore, in questo caso sono giustificati:

  • La mutazione è quasi completamente nascosta all'utente.
  • È altamente improbabile che qualcuno utilizzi l'enumeratore in modo confuso.
  • L'utilizzo di un tipo di valore mutabile risolve in realtà un problema di prestazioni realistico in uno scenario estremamente comune.

Mi chiedo se qualcuno sa perché iteratore generico di Array è stato implementato come un tipo di riferimento quando tante collezioni critici altre prestazioni utilizzate le strutture mutevoli posto.

Perché se siete il tipo di persona che è preoccupato per le prestazioni di enumerare una serie poi perché stai usando un enumeratore, in primo luogo? È un array per carità; basta scrivere un ciclo for che itera sui suoi indici come una persona normale e mai allocare l'enumeratore. (O un foreach ciclo, il compilatore C# riscriverà il ciclo foreach nell'equivalente for ciclo se si sa che la raccolta ciclo è un array.)

L'unico motivo per cui ci si ottiene un enumeratore da una matrice in il primo posto è se lo si passa ad un metodo che prende un IEnumerator<T>, nel qual caso se l'enumeratore è una struttura quindi lo si boxerà comunque. Perché prendere a spese di fare il tipo di valore e poi inscatolarlo? Basta fare un tipo di riferimento per cominciare.

+0

Data la prevalenza (e il potere espressivo) di LINQ, gli elenchi sono probabilmente recuperati per gli array abbastanza spesso. Per esempio, le classi di riflessione in .NET restituiscono array per molte proprietà/metodi - e usare LINQ w/reflection è abbastanza utile. A mia conoscenza, la maggior parte degli operatori LINQ non esegue controlli caso speciali per determinare se l'IEnumerable <> con cui hanno a che fare è in realtà un array. – LBushkin

+0

@LBushkin: corretto. Se non si è disposti ad assumere il costo di allocazione dell'heap e quindi di raccolta dei rifiuti di un * enumeratore *, si presume inoltre che non si desideri assumersi il costo di allocazione e raccolta dei dati inutili * della query * e di tutti gli oggetti di enumerazione creati e tutti i delegati che devi creare. Per non parlare di tutto il sovraccarico di chiamare quella query (controllando gli argomenti per essere corretto, e così via). LINQ agli oggetti è ragionevolmente performante ma non è stato certamente progettato per minimizzare le allocazioni dell'heap! LINQ alloca memoria heap in tutto il luogo. –

+0

Un altro fattore, ho il sospetto, è che l'enumeratore per l'originale 'Elenco' è stato progettato nei giorni precedenti ai generici. La struttura di 'List.Enumerator' riguarda solo tre tipi di codice: codice che utilizza esplicitamente il tipo' List.Enumerator', codice che accetta un parametro generico vincolato a 'IEnumerable', o codice che usa' var' per dichiarare un variabile che conterrà l'enumeratore di una lista. Se un programmatore sta per specificare 'List.Enumerator', il programmatore dovrebbe sapere cosa sta facendo. Per quanto riguarda gli altri due tipi di codice, non potevano esistere quando è stato progettato 'List.Enumerator'. – supercat

2

Gli array ricevono un trattamento speciale nel compilatore C#. Quando si utilizza foreach su di essi, il compilatore lo traduce in un ciclo for. Quindi non vi è alcun vantaggio in termini di prestazioni nell'utilizzo degli enumeratori struct.

List<T> d'altra parte è una classe semplice senza alcun trattamento speciale, quindi l'utilizzo di una struttura porta a prestazioni migliori.

+0

non è tutto foreach compilato in loop per? –

+1

@OskarKjellin no, i normali cicli 'foreach' diventano qualcosa come' while (enumerator.MoveNext()) {var element = enumerator.Current; ...} 'considerando che' foreach' scorre su un indice di array nell'array ('for (int i = 0; i CodesInChaos

+6

@OskarKjellin: Tutti i cicli' foreach' e 'for' sono * in definitiva * compilati in cicli' while'. Il punto di CodeInChaos è che i cicli 'foreach' sugli array sono in realtà prima compilato nel tradizionale ciclo di stile 'for (int i = 0; i

Problemi correlati