Quando dovrei usare una struct al posto di una class?

MSDN dice che dovresti usare le strutture quando hai bisogno di oggetti leggeri. Ci sono altri scenari quando una struttura è preferibile rispetto a una class?

Alcune persone potrebbero aver dimenticato che:

  1. le strutture possono avere metodi.
  2. le strutture non possono essere ereditate.

Capisco le differenze tecniche tra struct e classs, non ho proprio un buon feeling su quando utilizzare una struct.

MSDN ha la risposta: scegliere tra classi e strutture .

Fondamentalmente, quella pagina ti dà una checklist in 4 item e dice di usare una class a meno che il tuo tipo soddisfi tutti i criteri.

Non definire una struttura a meno che il tipo 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 incassato frequentemente.

Sono sorpreso di non aver letto in nessuna delle precedenti risposte, che considero l’aspetto più cruciale:

Io uso le strutture quando voglio un tipo senza identity framework. Ad esempio un punto 3D:

 public struct ThreeDimensionalPoint { public readonly int X, Y, Z; public ThreeDimensionalPoint(int x, int y, int z) { this.X = x; this.Y = y; this.Z = z; } public override string ToString() { return "(X=" + this.X + ", Y=" + this.Y + ", Z=" + this.Z + ")"; } public override int GetHashCode() { return (this.X + 2) ^ (this.Y + 2) ^ (this.Z + 2); } public override bool Equals(object obj) { if (!(obj is ThreeDimensionalPoint)) return false; ThreeDimensionalPoint other = (ThreeDimensionalPoint)obj; return this == other; } public static bool operator ==(ThreeDimensionalPoint p1, ThreeDimensionalPoint p2) { return p1.X == p2.X && p1.Y == p2.Y && p1.Z == p2.Z; } public static bool operator !=(ThreeDimensionalPoint p1, ThreeDimensionalPoint p2) { return !(p1 == p2); } } 

Se hai due istanze di questa struttura non ti interessa se sono un singolo pezzo di dati in memoria o due. Ti interessa solo il valore (s) in loro possesso.

Bill Wagner ha un capitolo su questo nel suo libro “c # efficace” ( http://www.amazon.com/Effective-Specific-Ways-Improve-Your/dp/0321245660 ). Conclude con il seguente principio:

  1. La responsabilità principale del tipo di archiviazione dei dati è la principale?
  2. La sua interfaccia pubblica è definita interamente da proprietà che accedono o modificano i suoi membri di dati?
  3. Sei sicuro che il tuo tipo non avrà mai sottoclassi?
  4. Sei sicuro che il tuo tipo non sarà mai trattato polimorficamente?

Se rispondi “sì” a tutte e 4 le domande: usa una struttura. Altrimenti, usa una class.

Utilizzare una struct quando si desidera la semantica del tipo di valore anziché il tipo di riferimento. Le strutture sono copia per valore quindi fai attenzione!

Vedi anche domande precedenti, ad es

Qual è la differenza tra struct e class in .NET?

Vorrei usare le strutture quando:

  1. si suppone che un object sia di sola lettura (ogni volta che si passa / si assegna una struttura viene copiata). Gli oggetti di sola lettura sono eccezionali quando si tratta di elaborazione multithread in quanto non richiedono il blocco nella maggior parte dei casi.

  2. un object è piccolo e di breve durata. In questo caso c’è una buona probabilità che l’object sia allocato nello stack, che è molto più efficiente di metterlo sull’heap gestito. Inoltre, la memoria allocata dall’object verrà liberata non appena esce dal suo ambito. In altre parole, è meno lavoro per Garbage Collector e la memoria viene utilizzata in modo più efficiente.

Utilizzare una class se:

  • La sua id quadro è importante. Le strutture vengono copiate implicitamente quando vengono passate per valore in un metodo.
  • Avrà un ampio ingombro di memoria.
  • I suoi campi hanno bisogno di inizializzatori.
  • È necessario ereditare da una class base.
  • Hai bisogno di un comportamento polimorfico;

Usa una struttura se:

  • Funzionerà come un tipo primitivo (int, long, byte, ecc.).
  • Deve avere un piccolo ingombro di memoria.
  • Stai chiamando un metodo P / Invoke che richiede che una struttura venga passata in base al valore.
  • È necessario ridurre l’impatto della garbage collection sul rendimento dell’applicazione.
  • I suoi campi devono essere inizializzati solo per i loro valori predefiniti. Questo valore sarebbe zero per i tipi numerici, false per i tipi booleani e null per i tipi di riferimento.
    • Si noti che nelle strutture C # 6.0 può avere un costruttore predefinito che può essere utilizzato per inizializzare i campi della struct con valori non predefiniti.
  • Non è necessario ereditare da una class base (diversa da ValueType, da cui ereditano tutte le strutture).
  • Non hai bisogno di un comportamento polimorfico.

Ho sempre usato una struct quando volevo raggruppare alcuni valori per passare le cose indietro da una chiamata al metodo, ma non avrò bisogno di usarlo per qualcosa dopo aver letto quei valori. Proprio come un modo per mantenere le cose pulite. Tendo a visualizzare le cose in una struttura come “throwaway” e le cose in una class più utili e “funzionali”

Se un’entity framework sarà immutabile, la questione se utilizzare una struttura o una class sarà generalmente una delle prestazioni piuttosto che una semantica. Su un sistema a 32/64 bit, i riferimenti di class richiedono 4/8 byte da memorizzare, indipendentemente dalla quantità di informazioni nella class; copiare un riferimento di class richiederà la copia di 4/8 byte. D’altra parte, ogni istanza di class distinta avrà 8/16 byte di overhead in aggiunta alle informazioni che contiene e il costo di memoria dei riferimenti ad esso. Supponiamo che si desideri un array di 500 quadro, ognuna con quattro interi a 32 bit. Se l’entity framework è un tipo di struttura, la matrice richiederà 8.000 byte indipendentemente dal fatto che tutte e 500 le entity framework siano tutte identiche, tutte diverse, o da qualche parte tra di esse. Se l’ quadro è un tipo di class, la matrice di 500 riferimenti richiederà 4.000 byte. Se tali riferimenti puntano tutti a oggetti diversi, gli oggetti richiederebbero 24 byte aggiuntivi ciascuno (12.000 byte per tutti i 500), per un totale di 16.000 byte – il doppio del costo di archiviazione di un tipo di struttura. D’altra parte, del codice creato un’istanza di object e quindi copiato un riferimento a tutti i 500 slot di array, il costo totale sarebbe 24 byte per quell’istanza e 4.000 per l’array – un totale di 4.024 byte. Un grande risparmio. Poche situazioni funzionerebbero bene come l’ultima, ma in alcuni casi potrebbe essere ansible copiare alcuni riferimenti a un numero sufficiente di slot per rendere utile tale condivisione.

Se si suppone che l’ quadro sia mutabile, la questione se utilizzare una class o una struttura è in qualche modo più semplice. Supponiamo che “Thing” sia una struct o una class che abbia un campo intero chiamato x, e uno faccia il seguente codice:

   Cosa t1, t2;
   ...
   t2 = t1;
   t2.x = 5;

Si vuole che quest’ultima affermazione abbia effetto su t1.x?

Se Thing è un tipo di class, t1 e t2 saranno equivalenti, il che significa che anche t1.xe t2.x saranno equivalenti. Quindi, la seconda affermazione interesserà t1.x. Se Thing è un tipo di struttura, t1 e t2 saranno istanze diverse, ovvero t1.xe t2.x si riferiranno a numeri interi diversi. Pertanto, la seconda istruzione non influirà su t1.x.

Le strutture mutevoli e le classi mutabili hanno comportamenti fondamentalmente diversi, sebbene .net abbia alcune stranezze nella gestione delle mutazioni delle strutture. Se si desidera un comportamento di tipo valore (il che significa che “t2 = t1” copierà i dati da t1 a t2 lasciando t1 e t2 come istanze distinte), e se si può vivere con i quirk nella gestione dei tipi di valore di .net, usare una struttura Se si desidera la semantica del tipo di valore, ma le stranezze di .net causerebbero l’interruzione della semantica del tipo di valore nella propria applicazione, usare una class e borbottare.

Inoltre le eccellenti risposte sopra:

Le strutture sono tipi di valore.

Non possono mai essere impostati su Nothing .

L’impostazione di una struttura = Nothing, imposterà tutti i suoi tipi di valori sui valori predefiniti.

quando non hai davvero bisogno di un comportamento, ma hai bisogno di più struttura di un semplice array o dizionario.

Follow-up Questo è il modo in cui penso alle strutture in generale. So che possono avere metodi, ma mi piace mantenere questa distinzione mentale generale.

Come dice @Simon, le strutture forniscono una semantica di tipo “valore”, quindi se hai bisogno di un comportamento simile a un tipo di dati incorporato, usa una struttura. Poiché le strutture vengono passate per copia, assicurati che siano di piccole dimensioni, circa 16 byte.

Hmm …

Non userei la garbage collection come argomento per / contro l’uso di structs vs classs. L’heap gestito funziona in modo molto simile a uno stack: la creazione di un object lo pone appena nella parte superiore dell’heap, che è quasi veloce quanto l’allocazione nello stack. Inoltre, se un object è di breve durata e non sopravvive a un ciclo di GC, la deallocazione è gratuita in quanto il GC funziona solo con memoria che è ancora accessibile. (Cerca MSDN, c’è una serie di articoli su gestione della memoria .NET, sono troppo pigro per scavare per loro).

Il più delle volte uso una struct, finisco per prendermi a calci per averlo fatto, perché in seguito scoprirò che avere una semantica di riferimento avrebbe reso le cose un po ‘più semplici.

Ad ogni modo, questi quattro punti nell’articolo MSDN pubblicato in precedenza sembrano una buona guida.

Le strutture sono sullo Stack e non sull’Heap, quindi sono filettate e dovrebbero essere utilizzate quando si implementa il modello dell’object di trasferimento, non si vogliono mai usare oggetti sull’Heap sono volatili, in questo caso si desidera utilizzare lo Stack chiamate, questo è un caso fondamentale per l’utilizzo di una struttura. Sono sorpreso da tutte le risposte di uscita qui,

Penso che la migliore risposta sia semplicemente quella di usare struct quando ciò di cui hai bisogno è una collezione di proprietà, class quando si tratta di una collezione di proprietà e comportamenti.