La fonte a cui fa riferimento l'OP ha una certa credibilità ... ma per quanto riguarda Microsoft: qual è la posizione sull'utilizzo della struttura? Ho cercato un po 'di apprendimento extra da Microsoft , ed ecco cosa ho trovato:
Prendi in considerazione la definizione di una struttura anziché di una classe se le istanze del tipo sono piccole e comunemente di breve durata o sono comunemente incorporate in altri oggetti.
Non definire una struttura a meno che il tipo non abbia tutte le seguenti caratteristiche:
- Rappresenta logicamente un singolo valore, simile ai tipi primitivi (intero, doppio e così via).
- Ha una dimensione dell'istanza inferiore a 16 byte.
- È immutabile
- Non dovrà essere inscatolato frequentemente.
Microsoft viola costantemente tali regole
Va bene, # 2 e # 3 comunque. Il nostro amato dizionario ha 2 strutture interne:
[StructLayout(LayoutKind.Sequential)] // default for structs
private struct Entry //<Tkey, TValue>
{
// View code at *Reference Source
}
[Serializable, StructLayout(LayoutKind.Sequential)]
public struct Enumerator :
IEnumerator<KeyValuePair<TKey, TValue>>, IDisposable,
IDictionaryEnumerator, IEnumerator
{
// View code at *Reference Source
}
* Fonte di riferimento
La fonte di "JonnyCantCode.com" ha ottenuto 3 su 4 - abbastanza perdonabile poiché il numero 4 probabilmente non sarebbe un problema. Se ti ritrovi a boxare una struttura, ripensa alla tua architettura.
Vediamo perché Microsoft dovrebbe usare queste strutture:
- Ogni struttura
Entry
e Enumerator
rappresenta singoli valori.
- Velocità
Entry
non viene mai passato come parametro al di fuori della classe del dizionario. Ulteriori ricerche dimostrano che, al fine di soddisfare l'implementazione di IEnumerable, Dictionary utilizza la Enumerator
struttura che copia ogni volta che viene richiesto un enumeratore ... ha senso.
- Interno alla classe del dizionario.
Enumerator
è pubblico perché Dizionario è enumerabile e deve avere uguale accessibilità all'implementazione dell'interfaccia IEnumerator, ad esempio IEnumerator getter.
Aggiornamento : inoltre, rendersi conto che quando una struttura implementa un'interfaccia - come fa Enumerator - e viene lanciata su quel tipo implementato, la struttura diventa un tipo di riferimento e viene spostata nell'heap. Interno alla classe Dictionary, Enumerator è ancora un tipo di valore. Tuttavia, non appena viene chiamato un metodo GetEnumerator()
, IEnumerator
viene restituito un tipo di riferimento .
Quello che non vediamo qui è alcun tentativo o prova del requisito per mantenere immutabili le strutture o mantenere una dimensione dell'istanza di soli 16 byte o meno:
- Nulla nelle strutture sopra è dichiarato
readonly
- non immutabile
- Le dimensioni di queste strutture potrebbero superare i 16 byte
Entry
ha una durata indeterminata (da Add()
, per Remove()
, Clear()
o raccolta dei rifiuti);
E ... 4. Entrambe le strutture memorizzano TKey e TValue, che sappiamo tutti essere abbastanza capaci di essere tipi di riferimento (informazioni aggiuntive sul bonus)
Nonostante i tasti tratteggiati, i dizionari sono in parte veloci in quanto l'istanza di una struttura è più rapida di un tipo di riferimento. Qui, ho un Dictionary<int, int>
che memorizza 300.000 numeri interi casuali con chiavi incrementalmente sequenzialmente.
Capacità: 312874 Dimensione
memoria: 2660827 byte
Ridimensionamento completato: 5 ms
Tempo totale di riempimento: 889 ms
Capacità : numero di elementi disponibili prima che l'array interno debba essere ridimensionato.
MemSize : determinato serializzando il dizionario in un MemoryStream e ottenendo una lunghezza in byte (abbastanza accurata per i nostri scopi).
Ridimensionamento completato : il tempo necessario per ridimensionare l'array interno da 150862 elementi a 312874 elementi. Quando pensi che ogni elemento sia copiato in sequenza via Array.CopyTo()
, non è troppo malandato.
Tempo totale da riempire : certamente distorto a causa della registrazione e di un OnResize
evento che ho aggiunto alla fonte; tuttavia, è comunque impressionante riempire 300k interi mentre si ridimensiona 15 volte durante l'operazione. Solo per curiosità, quale sarebbe il tempo totale da riempire se conoscessi già la capacità? 13ms
Quindi, ora, se Entry
fosse una classe? Questi tempi o metriche differirebbero così tanto?
Capacità: 312874 Dimensione
memoria: 2660827 byte
Ridimensionamento completato: 26 ms
Tempo totale di riempimento: 964 ms
Ovviamente, la grande differenza sta nel ridimensionamento. Qualche differenza se il dizionario è inizializzato con la capacità? Non abbastanza per preoccuparsi di ... 12ms .
Quello che succede è che, poiché Entry
è una struttura, non richiede l'inizializzazione come un tipo di riferimento. Questa è sia la bellezza che la rovina del tipo di valore. Per utilizzare Entry
come tipo di riferimento, ho dovuto inserire il seguente codice:
/*
* Added to satisfy initialization of entry elements --
* this is where the extra time is spent resizing the Entry array
* **/
for (int i = 0 ; i < prime ; i++)
{
destinationArray[i] = new Entry( );
}
/* *********************************************** */
Il motivo per cui ho dovuto inizializzare ciascun elemento dell'array Entry
come tipo di riferimento è disponibile in MSDN: Structure Design . In breve:
Non fornire un costruttore predefinito per una struttura.
Se una struttura definisce un costruttore predefinito, quando vengono create le matrici della struttura, Common Language Runtime esegue automaticamente il costruttore predefinito su ciascun elemento dell'array.
Alcuni compilatori, come il compilatore C #, non consentono alle strutture di avere costruttori predefiniti.
In realtà è abbastanza semplice e si prenderà in prestito dal di Asimov Tre leggi della robotica :
- La struttura deve essere sicura da usare
- La struttura deve svolgere la sua funzione in modo efficiente, a meno che ciò non violi la regola n. 1
- La struttura deve rimanere intatta durante l'uso a meno che non sia richiesta la sua distruzione per soddisfare la regola n. 1
... cosa ci toglie da questo : in breve, sii responsabile dell'uso dei tipi di valore. Sono veloci ed efficienti, ma hanno la capacità di causare molti comportamenti imprevisti se non adeguatamente mantenuti (ovvero copie involontarie).
System.Drawing.Rectangle
viola tutte e tre queste regole.