Quando dovrei usare una struct piuttosto che una classe in C #?


1391

Quando dovresti usare struct e non class in C #? Il mio modello concettuale è che le strutture vengono utilizzate nei momenti in cui l'oggetto è semplicemente una raccolta di tipi di valore . Un modo per tenerli logicamente tutti insieme in un insieme coeso.

Mi sono imbattuto in queste regole qui :

  • Una struttura dovrebbe rappresentare un singolo valore.
  • Una struttura dovrebbe avere un footprint di memoria inferiore a 16 byte.
  • Una struttura non deve essere modificata dopo la creazione.

Queste regole funzionano? Cosa vuol dire semanticamente una struttura?


248
System.Drawing.Rectangleviola tutte e tre queste regole.
ChrisW,

4
ci sono alcuni giochi commerciali scritti in C #, il punto è che sono usati per un codice ottimizzato
BlackTigerX

25
Le strutture offrono prestazioni migliori quando si hanno piccole raccolte di tipi di valore che si desidera raggruppare. Questo accade continuamente nella programmazione del gioco, ad esempio un vertice in un modello 3D avrà una posizione, una coordinata di trama e una normale, inoltre sarà generalmente immutabile. Un singolo modello può avere un paio di migliaia di vertici o può avere una dozzina, ma le strutture forniscono un overhead complessivo in questo scenario di utilizzo. Ho verificato questo attraverso il mio design del motore.
Chris D.


4
@ChrisW, vedo, ma quei valori non rappresentano un rettangolo, ovvero un valore "singolo"? Come Vector3D o Color, ci sono anche diversi valori all'interno, ma penso che rappresentino valori singoli?
Marson Mao,

Risposte:


604

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:

  1. Rappresenta logicamente un singolo valore, simile ai tipi primitivi (intero, doppio e così via).
  2. Ha una dimensione dell'istanza inferiore a 16 byte.
  3. È immutabile
  4. 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:

  1. Ogni struttura Entrye Enumeratorrappresenta singoli valori.
  2. Velocità
  3. Entrynon 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 Enumeratorstruttura che copia ogni volta che viene richiesto un enumeratore ... ha senso.
  4. 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(), IEnumeratorviene 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:

  1. Nulla nelle strutture sopra è dichiarato readonly- non immutabile
  2. Le dimensioni di queste strutture potrebbero superare i 16 byte
  3. Entryha 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 OnResizeevento 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 Entryfosse 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 Entrycome 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 Entrycome 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 :

  1. La struttura deve essere sicura da usare
  2. La struttura deve svolgere la sua funzione in modo efficiente, a meno che ciò non violi la regola n. 1
  3. 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).


8
Per quanto riguarda le regole di Microsoft, la regola sull'immutabilità sembra essere progettata per scoraggiare l'uso dei tipi di valore in modo tale che il loro comportamento differisca da quelli dei tipi di riferimento, nonostante il fatto che la semantica del valore mutabile a tratti possa essere utile . Se avere un tipo mutabile a tratti renderebbe più semplice il lavoro e se le posizioni di archiviazione del tipo dovrebbero essere logicamente separate l'una dall'altra, il tipo dovrebbe essere una struttura "mutabile".
supercat


2
Il fatto che molti tipi di Microsoft violino tali regole non rappresenta un problema con tali tipi, ma indica piuttosto che le regole non dovrebbero applicarsi a tutti i tipi di struttura. Se una struttura rappresenta una singola entità [come con Decimalo DateTime], quindi se non rispettasse le altre tre regole, dovrebbe essere sostituita da una classe. Se una struttura contiene una raccolta fissa di variabili, ognuna delle quali può contenere qualsiasi valore valido per il suo tipo [ad esempio Rectangle], allora dovrebbe attenersi a regole diverse , alcune delle quali sono contrarie a quelle per le strutture a "valore singolo" .
supercat

4
@IAbstract: alcune persone giustificherebbero il Dictionarytipo di voce sulla base del fatto che si tratta solo di un tipo interno, che le prestazioni sono state considerate più importanti della semantica o qualche altra scusa. Il mio punto è che un tipo come Rectangledovrebbe avere i suoi contenuti esposti come campi modificabili individualmente non "perché" i benefici delle prestazioni superano le risultanti imperfezioni semantiche, ma perché il tipo rappresenta semanticamente un insieme fisso di valori indipendenti , e quindi la struttura mutabile è sia più performante e semanticamente superiore .
supercat

2
@supercat: sono d'accordo ... e il punto centrale della mia risposta è che le "linee guida" sono piuttosto deboli e le strutture dovrebbero essere utilizzate con piena conoscenza e comprensione dei comportamenti. Vedere la mia risposta su struct mutevole qui: stackoverflow.com/questions/8108920/...
IAbstract

155

Ogni volta che si:

  1. non ho bisogno del polimorfismo,
  2. desidera una semantica di valore e
  3. desidera evitare l'allocazione dell'heap e l'overhead della garbage collection associata.

L'avvertenza, tuttavia, è che le strutture (arbitrariamente grandi) sono più costose da spostare rispetto ai riferimenti di classe (di solito una parola macchina), quindi le classi potrebbero finire per essere più veloci nella pratica.


1
Questo è solo un "avvertimento". Dovrebbe anche considerare il "sollevamento" di tipi di valore e casi come (Guid)null(va bene lanciare un null a un tipo di riferimento), tra le altre cose.

1
più costoso che in C / C ++? in C ++ il modo raccomandato è passare oggetti per valore
Ion Todirel,

@IonTodirel Non è stato per motivi di sicurezza della memoria, piuttosto che per le prestazioni? È sempre un compromesso, ma passare 32 B per stack sarà sempre più lento (TM) rispetto al passaggio di un riferimento 4 B per registro. Tuttavia , tieni presente anche che l'uso di "valore / riferimento" è leggermente diverso in C # e C ++: quando passi un riferimento a un oggetto, stai ancora passando per valore, anche se stai passando un riferimento (tu ' re passando il valore del riferimento, non un riferimento al riferimento, in sostanza). Non è una semantica di valore , ma è tecnicamente "pass-by-value".
Luaan,

@Luaan La copia è solo un aspetto dei costi. L'ulteriore riferimento indiretto dovuto al puntatore / riferimento costa anche per accesso. In alcuni casi la struttura può anche essere spostata e quindi non è nemmeno necessario copiarla.
Onur,

@Il nostro è interessante. Come si "sposta" senza copiare? Pensavo che l'istruzione asm "mov" non si "muovesse". Copia.
Winger Sendon,

148

Non sono d'accordo con le regole indicate nel post originale. Ecco le mie regole:

1) Le strutture vengono utilizzate per le prestazioni se archiviate in array. (vedi anche Quando sono le strutture la risposta? )

2) Ne hai bisogno nel codice che passa i dati strutturati a / da C / C ++

3) Non utilizzare le strutture a meno che non siano necessarie:

  • Si comportano in modo diverso dagli "oggetti normali" ( tipi di riferimento ) durante l'assegnazione e quando passano come argomenti, il che può portare a comportamenti imprevisti; questo è particolarmente pericoloso se la persona che guarda il codice non sa di avere a che fare con una struttura.
  • Non possono essere ereditati.
  • Passare le strutture come argomenti è più costoso delle classi.

4
+1 Sì, concordo pienamente sul n. 1 (questo è un enorme vantaggio quando si tratta di cose come immagini, ecc.) E per sottolineare che sono diversi dagli "oggetti normali" e che esiste un modo noto per conoscerlo se non dalle conoscenze esistenti o esaminando il tipo stesso. Inoltre, non è possibile eseguire il cast di un valore nullo in un tipo di struttura :-) Questo è in realtà un caso in cui quasi desidererei che ci fosse un po 'ungherese per tipi di valore non Core o una parola chiave obbligatoria' struct 'nel sito di dichiarazione delle variabili .

@pst: è vero che bisogna sapere qualcosa è structsapere come si comporterà, ma se qualcosa è a structcon campi esposti, è tutto ciò che si deve sapere. Se un oggetto espone una proprietà di un tipo di struttura di campo esposto e se il codice legge quella struttura in una variabile e la modifica, si può prevedere con sicurezza che tale azione non influirà sull'oggetto la cui proprietà è stata letta a meno che o fino a quando la struttura non viene scritta indietro. Al contrario, se la proprietà fosse un tipo di classe mutabile, la sua lettura e modifica potrebbe aggiornare l'oggetto sottostante come previsto, ma ...
supercat

... potrebbe anche finire per non cambiare nulla, oppure potrebbe cambiare o corrompere oggetti che non si intendeva cambiare. Avere codice la cui semantica dice "cambia questa variabile come preferisci; le modifiche non faranno nulla fino a quando non le memorizzi esplicitamente da qualche parte" sembra più chiaro del codice che dice "Stai ottenendo un riferimento a qualche oggetto, che potrebbe essere condiviso con qualsiasi numero di altri riferimenti o potrebbe non essere affatto condiviso; dovrai capire chi altri potrebbe avere riferimenti a questo oggetto per sapere cosa accadrà se lo cambi. "
supercat

Trova il numero 1. Un elenco pieno di strutture può comprimere dati molto più rilevanti nelle cache L1 / L2 rispetto a un elenco pieno di riferimenti a oggetti (per la struttura della dimensione giusta).
Matt Stephenson,

2
L'ereditarietà è raramente lo strumento giusto per il lavoro e ragionare troppo sulle prestazioni senza profilare è una cattiva idea. Innanzitutto, le strutture possono essere passate per riferimento. In secondo luogo, il passaggio per riferimento o per valore è raramente un problema di prestazioni significativo. Infine, non stai tenendo conto dell'allocazione aggiuntiva dell'heap e della garbage collection che devono aver luogo per una classe. Personalmente, preferisco pensare alle strutture come semplici dati e classi come cose che fanno cose (oggetti) sebbene sia possibile definire metodi anche su strutture.
weberc2,

88

Utilizzare uno struct quando si desidera la semantica di valore anziché la semantica di riferimento.

modificare

Non sono sicuro del motivo per cui la gente sta effettuando il downvoting di questo, ma questo è un punto valido, ed è stato fatto prima che l'operazione chiarisse la sua domanda, ed è la ragione fondamentale di base per una struttura.

Se hai bisogno di una semantica di riferimento, hai bisogno di una classe e non di una struttura.


13
Tutti lo sanno. Sembra che stia cercando qualcosa di più di una risposta "strutt is a value type".
TheSmurf,

21
È il caso più semplice e dovrebbe essere indicato per chiunque legga questo post e non lo sappia.
JoshBerke,

3
Non che questa risposta non sia vera; ovviamente lo è. Non è proprio questo il punto.
TheSmurf,

55
@Josh: Per chiunque non lo sappia già, dire semplicemente che è una risposta insufficiente, poiché è molto probabile che non sappiano nemmeno cosa significhi.
TheSmurf,

1
Ho appena retrocesso questo perché penso che una delle altre risposte dovrebbe essere in alto - qualsiasi risposta che dice "Per l'interoperabilità con codice non gestito, evita altrimenti".
Daniel Earwicker,

59

Oltre alla risposta "è un valore", uno scenario specifico per l'utilizzo delle strutture è quando sai di avere un set di dati che causa problemi di garbage collection e di avere molti oggetti. Ad esempio, un ampio elenco / array di istanze Person. La metafora naturale qui è una classe, ma se hai un gran numero di istanze Person di lunga durata, possono finire per intasare GEN-2 e causare bancarelle GC. Se lo scenario lo giustifica, un potenziale approccio qui è quello di utilizzare un array (non un elenco) di strutture Person , ad es Person[]. Ora, invece di avere milioni di oggetti in GEN-2, hai un singolo pezzo sul LOH (suppongo che non ci siano stringhe ecc. Qui - cioè un valore puro senza riferimenti). Questo ha un impatto GC minimo.

Lavorare con questi dati è imbarazzante, poiché i dati sono probabilmente sovradimensionati per una struttura e non si desidera copiare sempre valori di grasso. Tuttavia, accedervi direttamente in un array non copia la struttura - è sul posto (contrariamente a un indicizzatore di elenco, che copia). Ciò significa molto lavoro con gli indici:

int index = ...
int id = peopleArray[index].Id;

Si noti che mantenere immutabili i valori stessi aiuterà qui. Per una logica più complessa, utilizzare un metodo con un parametro by-ref:

void Foo(ref Person person) {...}
...
Foo(ref peopleArray[index]);

Ancora una volta, questo è a posto - non abbiamo copiato il valore.

In scenari molto specifici, questa tattica può avere molto successo; tuttavia, è uno scernario abbastanza avanzato che dovrebbe essere tentato solo se sai cosa stai facendo e perché. L'impostazione predefinita qui sarebbe una classe.


+1 risposta interessante. Saresti disposto a condividere eventuali aneddoti del mondo reale su tale approccio in uso?
Jordão,

@Jordao su cellulare, ma cerca su Google: + gravell + "assalto di GC"
Marc Gravell

1
Molte grazie. L'ho trovato qui .
Jordão,

2
@MarcGravell Perché hai menzionato: utilizzare un array (non un elenco) ? ListCredo, usa un Arraydietro le quinte. no ?
Royi Namir,

4
@RoyiNamir Anche io ero curioso di questo, ma credo che la risposta risieda nel secondo paragrafo della risposta di Marc. "Tuttavia, accedervi direttamente in un array non copia la struttura - è sul posto (contrariamente a un indicizzatore di elenco, che copia)."
user1323245

40

Dalle specifiche del linguaggio C # :

1.7 Strutture

Come le classi, le strutture sono strutture di dati che possono contenere membri di dati e membri di funzioni, ma a differenza delle classi, le strutture sono tipi di valore e non richiedono allocazione di heap. Una variabile di un tipo struct memorizza direttamente i dati della struttura, mentre una variabile di un tipo di classe memorizza un riferimento a un oggetto allocato dinamicamente. I tipi di struttura non supportano l'ereditarietà specificata dall'utente e tutti i tipi di struttura ereditano implicitamente dall'oggetto tipo.

Le strutture sono particolarmente utili per le piccole strutture di dati con semantica di valore. Numeri complessi, punti in un sistema di coordinate o coppie chiave-valore in un dizionario sono tutti buoni esempi di strutture. L'uso di strutture anziché di classi per strutture di dati di piccole dimensioni può fare una grande differenza nel numero di allocazioni di memoria eseguite da un'applicazione. Ad esempio, il seguente programma crea e inizializza un array di 100 punti. Con Point implementato come classe, vengono istanziati 101 oggetti separati, uno per l'array e uno per i 100 elementi.

class Point
{
   public int x, y;

   public Point(int x, int y) {
      this.x = x;
      this.y = y;
   }
}

class Test
{
   static void Main() {
      Point[] points = new Point[100];
      for (int i = 0; i < 100; i++) points[i] = new Point(i, i);
   }
}

Un'alternativa è rendere Point una struttura.

struct Point
{
   public int x, y;

   public Point(int x, int y) {
      this.x = x;
      this.y = y;
   }
}

Ora, viene istanziato solo un oggetto, quello per l'array, e le istanze Point vengono archiviate in linea nell'array.

I costruttori di Struct sono richiamati con il nuovo operatore, ma ciò non implica che la memoria sia allocata. Invece di allocare in modo dinamico un oggetto e restituire un riferimento ad esso, un costruttore di strutture restituisce semplicemente il valore di struttura stesso (in genere in una posizione temporanea sullo stack) e questo valore viene quindi copiato come necessario.

Con le classi, è possibile che due variabili facciano riferimento allo stesso oggetto e quindi che le operazioni su una variabile influenzino l'oggetto a cui fa riferimento l'altra variabile. Con le strutture, le variabili hanno ciascuna una propria copia dei dati e non è possibile che le operazioni su uno influiscano sull'altro. Ad esempio, l'output prodotto dal seguente frammento di codice dipende dal fatto che Point sia una classe o una struttura.

Point a = new Point(10, 10);
Point b = a;
a.x = 20;
Console.WriteLine(b.x);

Se Point è una classe, l'output è 20 perché aeb fanno riferimento allo stesso oggetto. Se Point è una struttura, l'output è 10 perché l'assegnazione di a a b crea una copia del valore e questa copia non è influenzata dalla successiva assegnazione a ax

L'esempio precedente evidenzia due dei limiti delle strutture. Innanzitutto, la copia di un'intera struttura è in genere meno efficiente della copia di un riferimento a un oggetto, quindi il passaggio di parametri di assegnazione e valore può essere più costoso con le strutture che con i tipi di riferimento. In secondo luogo, ad eccezione dei parametri ref e out, non è possibile creare riferimenti a strutture, il che ne esclude l'utilizzo in diverse situazioni.


4
Mentre il fatto che i riferimenti a strutture non possano essere mantenuti a volte è una limitazione, è anche una caratteristica molto utile. Uno dei principali punti deboli di .net è che non esiste un modo decente per passare un codice esterno a un riferimento a un oggetto mutabile senza perdere per sempre il controllo di tale oggetto. Al contrario, si può tranquillamente dare un metodo esterno refa una struttura mutabile e sapere che qualsiasi mutazione che il metodo esterno eseguirà su di esso verrà eseguita prima che ritorni. Peccato .net non ha alcun concetto di parametri effimeri e valori di ritorno della funzione, dal momento che ...
supercat

4
... ciò consentirebbe di ottenere la vantaggiosa semantica delle strutture passate refcon oggetti di classe. In sostanza, le variabili locali, i parametri e i valori di ritorno della funzione potrebbero essere persistenti (impostazione predefinita), restituibili o effimeri. Al codice sarebbe vietato copiare cose effimere in qualsiasi cosa sopravviverebbe allo scopo attuale. Le cose restituibili sarebbero come cose effimere, tranne che potrebbero essere restituite da una funzione. Il valore di ritorno di una funzione sarebbe vincolato dalle restrizioni più rigorose applicabili a uno qualsiasi dei suoi parametri "restituibili".
supercat,

34

Le strutture sono utili per la rappresentazione atomica di dati, in cui tali dati possono essere copiati più volte dal codice. La clonazione di un oggetto è in genere più costosa della copia di una struttura, poiché comporta l'allocazione della memoria, l'esecuzione del costruttore e la deallocazione / garbage collection al termine.


4
Sì, ma le strutture di grandi dimensioni possono essere più costose dei riferimenti di classe (quando si passa ai metodi).
Alex,

27

Ecco una regola di base.

  • Se tutti i campi membro sono tipi di valore, creare una struttura .

  • Se un campo membro è un tipo di riferimento, creare una classe . Questo perché il campo del tipo di riferimento richiederà comunque l'allocazione dell'heap.

exmaples

public struct MyPoint 
{
    public int X; // Value Type
    public int Y; // Value Type
}

public class MyPointWithName 
{
    public int X; // Value Type
    public int Y; // Value Type
    public string Name; // Reference Type
}

3
Tipi di riferimento immutabili come stringsono semanticamente equivalenti ai valori e la memorizzazione di un riferimento a un oggetto immutabile in un campo non comporta un'allocazione dell'heap. La differenza tra una struttura con campi pubblici esposti e un oggetto classe con campi pubblici esposti è che, data la sequenza di codice var q=p; p.X=4; q.X=5;, p.Xavrà il valore 4 se aè un tipo di struttura e 5 se è un tipo di classe. Se si desidera essere in grado di modificare comodamente i membri del tipo, è necessario selezionare "class" o "struct" in base al fatto che si desideri che le modifiche abbiano qeffetto p.
supercat

Sì, sono d'accordo che la variabile di riferimento sarà nello stack ma l'oggetto a cui fa riferimento esisterà nell'heap. Sebbene le strutture e le classi si comportino in modo diverso quando assegnate a una variabile diversa, ma non credo sia un forte fattore decisivo.
Usman Zafar,

Le strutture mutabili e le classi mutabili si comportano in modo completamente diverso; se uno ha ragione, molto probabilmente l'altro avrà torto. Non sono sicuro di come il comportamento non sia un fattore decisivo nel determinare se utilizzare una struttura o una classe.
supercat

Ho detto che non è un forte fattore decisivo perché spesso quando crei una classe o una struttura non sei sicuro di come verrà utilizzato. Quindi ti concentri su come le cose hanno più senso dal punto di vista del design. Comunque non ho mai visto in un unico posto nella libreria .NET in cui uno struct contiene una variabile di riferimento.
Usman Zafar,

1
Il tipo di struttura ArraySegment<T>incapsula a T[], che è sempre un tipo di classe. Il tipo di struttura KeyValuePair<TKey,TValue>viene spesso utilizzato con i tipi di classe come parametri generici.
supercat

19

Primo: scenari di interoperabilità o quando è necessario specificare il layout di memoria

Secondo: quando i dati hanno quasi le stesse dimensioni di un puntatore di riferimento comunque.


17

È necessario utilizzare una "struttura" nelle situazioni in cui si desidera specificare esplicitamente il layout di memoria utilizzando StructLayoutAttribute , in genere per PInvoke.

Modifica: il commento sottolinea che è possibile utilizzare class o struct con StructLayoutAttribute e questo è certamente vero. In pratica, in genere si utilizza una struttura: è allocata nello stack rispetto all'heap, il che ha senso se si sta semplicemente passando un argomento a una chiamata di metodo non gestita.


5
StructLayoutAttribute può essere applicato a strutture o classi, quindi questo non è un motivo per usare le strutture.
Stephen Martin,

Perché ha senso se si sta semplicemente passando un argomento a una chiamata di metodo non gestita?
David Klempfner,

16

Uso le strutture per impacchettare o disimballare qualsiasi tipo di formato di comunicazione binaria. Ciò include la lettura o la scrittura su disco, gli elenchi di vertici DirectX, i protocolli di rete o la gestione di dati crittografati / compressi.

Le tre linee guida che elenchi non mi sono state utili in questo contesto. Quando avrò bisogno di scrivere quattrocento byte di roba in un Ordine particolare, definirò una struttura di quattrocento byte e la riempirò con tutti i valori non correlati che dovrebbe avere, e sto andando per impostarlo in qualsiasi modo abbia più senso in quel momento. (Va bene, quattrocento byte sarebbero piuttosto strani-- ma quando stavo scrivendo file Excel per vivere, avevo a che fare con strutture fino a una quarantina di byte dappertutto, perché è grande quanto sono alcuni dei record BIFF.)


Non potresti usare altrettanto facilmente un tipo di riferimento per quello?
David Klempfner,

15

Con l'eccezione dei tipi di valore utilizzati direttamente dal runtime e vari altri scopi di PInvoke, è necessario utilizzare i tipi di valore in 2 scenari.

  1. Quando hai bisogno della semantica della copia.
  2. Quando è necessaria l'inizializzazione automatica, normalmente in array di questi tipi.

Il n. 2 sembra essere parte del motivo della prevalenza della struttura nelle classi di raccolta .Net.
Estratto IA

Se la prima cosa da fare quando si crea una posizione di archiviazione di un tipo di classe è creare una nuova istanza di quel tipo, memorizzare un riferimento in quella posizione e non copiare mai il riferimento altrove o sovrascriverlo, allora una struttura e la classe si comporterebbe in modo identico. Le strutture hanno un modo standard conveniente per copiare tutti i campi da un'istanza all'altra e in genere offriranno prestazioni migliori nei casi in cui non si duplicerebbe mai un riferimento a una classe (ad eccezione del thisparametro effimero usato per invocare i suoi metodi); le classi consentono di duplicare i riferimenti.
supercat,

13

.NET supporta value typese reference types(in Java, è possibile definire solo i tipi di riferimento). Le istanze reference typesvengono allocate nell'heap gestito e vengono raccolte in modo inutile quando non vi sono riferimenti in sospeso a esse. value typesD'altra parte, le istanze di sono allocate nella stackmemoria, e quindi la memoria allocata viene recuperata non appena termina il loro ambito. E, naturalmente, fatti value typespassare per valore e reference typesper riferimento. Tutti i tipi di dati primitivi C #, ad eccezione di System.String, sono tipi di valore.

Quando usare struct over class,

In C #, structssono value types, le classi sono reference types. Puoi creare tipi di valore, in C #, usando la enumparola chiave e la structparola chiave. L'uso di al value typeposto di a reference typecomporterà un minor numero di oggetti sull'heap gestito, con conseguente minor carico sul Garbage Collector (GC), cicli GC meno frequenti e conseguentemente migliori prestazioni. Tuttavia, value typeshanno anche i loro lati negativi. Passare intorno a un grosso structè decisamente più costoso di passare un riferimento, questo è un problema evidente. L'altro problema è il sovraccarico associato boxing/unboxing. Nel caso ti stia chiedendo cosa boxing/unboxingsignifichi, segui questi link per una buona spiegazione su boxingeunboxing. A parte le prestazioni, ci sono momenti in cui hai semplicemente bisogno di tipi per avere una semantica di valore, il che sarebbe molto difficile (o brutto) da implementare se reference typestutto ciò che hai. È consigliabile utilizzare value typessolo, quando è necessario copiare la semantica o è necessaria l'inizializzazione automatica, normalmente in arraysquesti tipi.


Copiare piccole strutture o passare per valore è economico quanto copiare o passare un riferimento di classe o passare le strutture ref. Il passaggio di qualsiasi struttura di dimensioni in base al refcosto equivale al passaggio di un riferimento di classe in base al valore. Copiare qualsiasi struttura di dimensioni o passare per valore è più economico che eseguire una copia difensiva di un oggetto di classe e memorizzare o passare un riferimento a quello. Le classi big times sono migliori di quanto le strutture per l'archiviazione dei valori siano (1) quando le classi sono immutabili (in modo da evitare la copia difensiva), e ogni istanza creata verrà passata molto, o ...
supercat

... (2) quando per varie ragioni una struttura non sarebbe semplicemente utilizzabile [ad esempio perché si devono usare riferimenti annidati per qualcosa come un albero, o perché si ha bisogno di un polimorfismo]. Si noti che quando si utilizzano i tipi di valore, in genere si dovrebbero esporre i campi direttamente assenti a un motivo particolare per non (mentre con la maggior parte dei tipi di classe i campi devono essere racchiusi tra le proprietà). Molti dei cosiddetti "mali" di tipi di valori mutabili derivano da un involucro inutile di campi nelle proprietà (ad esempio, mentre alcuni compilatori consentirebbero di chiamare un setter di proprietà su una struttura di sola lettura perché a volte ...
supercat

... fare la cosa giusta, tutti i compilatori respingerebbero correttamente i tentativi di impostare direttamente campi su tali strutture; il modo migliore per garantire il rifiuto dei compilatori readOnlyStruct.someMember = 5;non è quello di creare someMemberuna proprietà di sola lettura, ma piuttosto di renderla un campo.
supercat,

12

Una struttura è un tipo di valore. Se si assegna una struttura a una nuova variabile, la nuova variabile conterrà una copia dell'originale.

public struct IntStruct {
    public int Value {get; set;}
}

L'esecuzione dei seguenti risultati in 5 istanze della struttura archiviata in memoria:

var struct1 = new IntStruct() { Value = 0 }; // original
var struct2 = struct1;  // A copy is made
var struct3 = struct2;  // A copy is made
var struct4 = struct3;  // A copy is made
var struct5 = struct4;  // A copy is made

// NOTE: A "copy" will occur when you pass a struct into a method parameter.
// To avoid the "copy", use the ref keyword.

// Although structs are designed to use less system resources
// than classes.  If used incorrectly, they could use significantly more.

Una classe è un tipo di riferimento. Quando si assegna una classe a una nuova variabile, la variabile contiene un riferimento all'oggetto classe originale.

public class IntClass {
    public int Value {get; set;}
}

L'esecuzione di quanto segue comporta solo un'istanza dell'oggetto di classe in memoria.

var class1 = new IntClass() { Value = 0 };
var class2 = class1;  // A reference is made to class1
var class3 = class2;  // A reference is made to class1
var class4 = class3;  // A reference is made to class1
var class5 = class4;  // A reference is made to class1  

Struct s può aumentare la probabilità di un errore del codice. Se un oggetto valore viene trattato come un oggetto di riferimento mutabile, uno sviluppatore potrebbe rimanere sorpreso quando le modifiche apportate vengono perse inaspettatamente.

var struct1 = new IntStruct() { Value = 0 };
var struct2 = struct1;
struct2.Value = 1;
// At this point, a developer may be surprised when 
// struct1.Value is 0 and not 1

12

Ho fatto un piccolo benchmark con BenchmarkDotNet per comprendere meglio i vantaggi della "struttura" numerica. Sto testando il looping attraverso array (o elenco) di strutture (o classi). La creazione di tali array o elenchi non rientra nell'ambito del benchmark: è chiaro che la "classe" più pesante utilizzerà più memoria e coinvolgerà GC.

Quindi la conclusione è: state attenti con LINQ e boxing / unboxing di strutture nascoste e l'utilizzo di strutture per le microottimizzazioni è strettamente correlato alle matrici.

PS Un altro punto di riferimento sul passaggio di struct / class attraverso lo stack di chiamate è lì https://stackoverflow.com/a/47864451/506147

BenchmarkDotNet=v0.10.8, OS=Windows 10 Redstone 2 (10.0.15063)
Processor=Intel Core i5-2500K CPU 3.30GHz (Sandy Bridge), ProcessorCount=4
Frequency=3233542 Hz, Resolution=309.2584 ns, Timer=TSC
  [Host] : Clr 4.0.30319.42000, 64bit RyuJIT-v4.7.2101.1
  Clr    : Clr 4.0.30319.42000, 64bit RyuJIT-v4.7.2101.1
  Core   : .NET Core 4.6.25211.01, 64bit RyuJIT


          Method |  Job | Runtime |      Mean |     Error |    StdDev |       Min |       Max |    Median | Rank |  Gen 0 | Allocated |
---------------- |----- |-------- |----------:|----------:|----------:|----------:|----------:|----------:|-----:|-------:|----------:|
   TestListClass |  Clr |     Clr |  5.599 us | 0.0408 us | 0.0382 us |  5.561 us |  5.689 us |  5.583 us |    3 |      - |       0 B |
  TestArrayClass |  Clr |     Clr |  2.024 us | 0.0102 us | 0.0096 us |  2.011 us |  2.043 us |  2.022 us |    2 |      - |       0 B |
  TestListStruct |  Clr |     Clr |  8.427 us | 0.1983 us | 0.2204 us |  8.101 us |  9.007 us |  8.374 us |    5 |      - |       0 B |
 TestArrayStruct |  Clr |     Clr |  1.539 us | 0.0295 us | 0.0276 us |  1.502 us |  1.577 us |  1.537 us |    1 |      - |       0 B |
   TestLinqClass |  Clr |     Clr | 13.117 us | 0.1007 us | 0.0892 us | 13.007 us | 13.301 us | 13.089 us |    7 | 0.0153 |      80 B |
  TestLinqStruct |  Clr |     Clr | 28.676 us | 0.1837 us | 0.1534 us | 28.441 us | 28.957 us | 28.660 us |    9 |      - |      96 B |
   TestListClass | Core |    Core |  5.747 us | 0.1147 us | 0.1275 us |  5.567 us |  5.945 us |  5.756 us |    4 |      - |       0 B |
  TestArrayClass | Core |    Core |  2.023 us | 0.0299 us | 0.0279 us |  1.990 us |  2.069 us |  2.013 us |    2 |      - |       0 B |
  TestListStruct | Core |    Core |  8.753 us | 0.1659 us | 0.1910 us |  8.498 us |  9.110 us |  8.670 us |    6 |      - |       0 B |
 TestArrayStruct | Core |    Core |  1.552 us | 0.0307 us | 0.0377 us |  1.496 us |  1.618 us |  1.552 us |    1 |      - |       0 B |
   TestLinqClass | Core |    Core | 14.286 us | 0.2430 us | 0.2273 us | 13.956 us | 14.678 us | 14.313 us |    8 | 0.0153 |      72 B |
  TestLinqStruct | Core |    Core | 30.121 us | 0.5941 us | 0.5835 us | 28.928 us | 30.909 us | 30.153 us |   10 |      - |      88 B |

Codice:

[RankColumn, MinColumn, MaxColumn, StdDevColumn, MedianColumn]
    [ClrJob, CoreJob]
    [HtmlExporter, MarkdownExporter]
    [MemoryDiagnoser]
    public class BenchmarkRef
    {
        public class C1
        {
            public string Text1;
            public string Text2;
            public string Text3;
        }

        public struct S1
        {
            public string Text1;
            public string Text2;
            public string Text3;
        }

        List<C1> testListClass = new List<C1>();
        List<S1> testListStruct = new List<S1>();
        C1[] testArrayClass;
        S1[] testArrayStruct;
        public BenchmarkRef()
        {
            for(int i=0;i<1000;i++)
            {
                testListClass.Add(new C1  { Text1= i.ToString(), Text2=null, Text3= i.ToString() });
                testListStruct.Add(new S1 { Text1 = i.ToString(), Text2 = null, Text3 = i.ToString() });
            }
            testArrayClass = testListClass.ToArray();
            testArrayStruct = testListStruct.ToArray();
        }

        [Benchmark]
        public int TestListClass()
        {
            var x = 0;
            foreach(var i in testListClass)
            {
                x += i.Text1.Length + i.Text3.Length;
            }
            return x;
        }

        [Benchmark]
        public int TestArrayClass()
        {
            var x = 0;
            foreach (var i in testArrayClass)
            {
                x += i.Text1.Length + i.Text3.Length;
            }
            return x;
        }

        [Benchmark]
        public int TestListStruct()
        {
            var x = 0;
            foreach (var i in testListStruct)
            {
                x += i.Text1.Length + i.Text3.Length;
            }
            return x;
        }

        [Benchmark]
        public int TestArrayStruct()
        {
            var x = 0;
            foreach (var i in testArrayStruct)
            {
                x += i.Text1.Length + i.Text3.Length;
            }
            return x;
        }

        [Benchmark]
        public int TestLinqClass()
        {
            var x = testListClass.Select(i=> i.Text1.Length + i.Text3.Length).Sum();
            return x;
        }

        [Benchmark]
        public int TestLinqStruct()
        {
            var x = testListStruct.Select(i => i.Text1.Length + i.Text3.Length).Sum();
            return x;
        }
    }

Hai capito perché le strutture sono molto più lente se usate in liste e simili? È a causa della boxe nascosta e del unboxing che hai menzionato? Se è così, perché succede?
Marko Grdinic,

L'accesso a struct in array dovrebbe essere più rapido solo perché non sono necessari riferimenti aggiuntivi. Boxe / Unboxing è il caso di Linq.
Roman Pokrovskij

10

I tipi di struttura in C # o altri linguaggi .net vengono generalmente utilizzati per contenere elementi che dovrebbero comportarsi come gruppi di valori di dimensioni fisse. Un aspetto utile dei tipi di struttura è che i campi di un'istanza di tipo struttura possono essere modificati modificando la posizione di archiviazione in cui si trova e in nessun altro modo. È possibile codificare una struttura in modo tale che l'unico modo per mutare qualsiasi campo sia costruire un'istanza completamente nuova e quindi utilizzare un'assegnazione struct per mutare tutti i campi del target sovrascrivendoli con i valori della nuova istanza, ma a meno che una struttura non fornisca alcun mezzo per creare un'istanza in cui i suoi campi abbiano valori non predefiniti, tutti i suoi campi saranno mutabili se e se la struttura stessa è memorizzata in una posizione mutabile.

Si noti che è possibile progettare un tipo di struttura in modo che si comporti essenzialmente come un tipo di classe, se la struttura contiene un campo di tipo di classe privato e reindirizza i propri membri a quello dell'oggetto di classe avvolto. Ad esempio, a PersonCollectionpotrebbe offrire proprietà SortedByNamee SortedById, entrambe le quali contengono un riferimento "immutabile" a un PersonCollection(impostato nel loro costruttore) e implementano GetEnumeratorchiamando creator.GetNameSortedEnumeratoro creator.GetIdSortedEnumerator. Tali strutture si comporterebbero in modo molto simile a un riferimento a PersonCollection, tranne per il fatto che i loro GetEnumeratormetodi sarebbero legati a metodi diversi in PersonCollection. Si potrebbe anche avere una struttura che avvolge una porzione di un array (ad esempio si potrebbe definire una ArrayRange<T>struttura che potrebbe contenere un T[]chiamato Arr, un int Offsete un intLength, con una proprietà indicizzata alla quale, per un indice idxnell'intervallo da 0 a Length-1, accederebbe Arr[idx+Offset]). Sfortunatamente, se si footratta di un'istanza di sola lettura di tale struttura, le attuali versioni del compilatore non consentiranno operazioni come foo[3]+=4;perché non hanno modo di determinare se tali operazioni tenterebbero di scrivere nei campi di foo.

È anche possibile progettare una struttura in modo da comportarsi come un tipo di valore che contiene una raccolta di dimensioni variabili (che sembrerà essere copiata ogni volta che la struttura è) ma l'unico modo per farlo funziona è quello di garantire che nessun oggetto a cui struct detiene un riferimento sarà mai esposto a qualsiasi cosa possa mutarlo. Ad esempio, si potrebbe avere una struttura simile a un array che contiene un array privato e il cui metodo "put" indicizzato crea un nuovo array il cui contenuto è simile a quello dell'originale tranne un elemento modificato. Sfortunatamente, può essere in qualche modo difficile far funzionare in modo efficiente tali strutture. Mentre ci sono momenti in cui la semantica della struttura può essere conveniente (ad esempio essere in grado di passare una raccolta simile ad un array a una routine, con il chiamante e il chiamante entrambi consapevoli che il codice esterno non modificherà la raccolta,


10

Nah - Non sono completamente d'accordo con le regole. Sono buone linee guida da considerare con prestazioni e standardizzazione, ma non alla luce delle possibilità.

Come puoi vedere nelle risposte, ci sono molti modi creativi per usarli. Quindi, queste linee guida devono essere proprio questo, sempre per motivi di prestazioni ed efficienza.

In questo caso, utilizzo le classi per rappresentare oggetti del mondo reale nella loro forma più ampia, uso le strutture per rappresentare oggetti più piccoli che hanno usi più precisi. Il modo in cui l'hai detto, "un insieme più coeso". La parola chiave è coesivo. Le classi saranno più elementi orientati agli oggetti, mentre le strutture possono avere alcune di quelle caratteristiche, sebbene su scala minore. IMO.

Li uso molto nei tag Treeview e Listview in cui è possibile accedere molto rapidamente agli attributi statici comuni. Ho sempre lottato per ottenere queste informazioni in un altro modo. Ad esempio, nelle mie applicazioni di database, utilizzo un Treeview in cui ho tabelle, SP, funzioni o qualsiasi altro oggetto. Creo e popolo la mia struttura, la inserisco nel tag, la tiro fuori, ottengo i dati della selezione e così via. Non lo farei con una lezione!

Cerco di mantenerli piccoli, li uso in situazioni di istanza singola e impedisco loro di cambiare. È prudente essere consapevoli della memoria, dell'allocazione e delle prestazioni. E i test sono così necessari.


Le strutture possono essere sensibilmente utilizzate per rappresentare oggetti leggeri immutabili, oppure possono essere sensibilmente utilizzate per rappresentare insiemi fissi di variabili correlate ma indipendenti (ad esempio le coordinate di un punto). Il consiglio in quella pagina è buono per le strutture che sono progettate per servire il primo scopo, ma è sbagliato per le strutture che sono progettate per servire il secondo scopo. Il mio pensiero attuale è che le strutture che hanno campi privati ​​dovrebbero generalmente soddisfare la descrizione indicata, ma molte strutture dovrebbero esporre il loro intero stato attraverso campi pubblici.
supercat

Se la specifica per un tipo di "punto 3d" indica che il suo intero stato è esposto tramite membri leggibili x, ye z ed è possibile creare un'istanza con qualsiasi combinazione di doublevalori per tali coordinate, tale specifica la costringerebbe a comportarsi in modo semanticamente identico a una struttura a campo esposto, ad eccezione di alcuni dettagli del comportamento multi-thread (la classe immutabile sarebbe migliore in alcuni casi, mentre la struttura a campo esposto sarebbe migliore in altri; una cosiddetta struttura "immutabile" sarebbe peggio in ogni caso).
supercat

8

La mia regola è

1, usa sempre la classe;

2, Se si verificano problemi di prestazioni, provo a modificare alcune classi per strutturare a seconda delle regole menzionate da @IAbstract, quindi eseguo un test per vedere se queste modifiche possono migliorare le prestazioni.


Un caso d'uso sostanziale che Microsoft ignora è quando si desidera che una variabile di tipo Fooincapsuli una raccolta fissa di valori indipendenti (ad es. Coordinate di un punto) che a volte si vorrà passare in gruppo e talvolta si desidera modificare in modo indipendente. Non ho trovato alcun modello per l'utilizzo di classi che combini entrambi gli scopi in modo altrettanto semplice di una semplice struttura a campo esposto (che, essendo una raccolta fissa di variabili indipendenti, si adatta perfettamente al conto).
supercat

1
@supercat: penso che non sia del tutto giusto dare la colpa a Microsoft per questo. Il vero problema qui è che C # come linguaggio orientato agli oggetti semplicemente non si concentra su tipi di record semplici che espongono i dati solo senza molto comportamento. C # non è un linguaggio multi-paradigma nella stessa misura in cui lo è C ++. Detto questo, credo anche che pochissime persone programmino OOP puro, quindi forse C # è un linguaggio troppo idealistico. (Io per primo ho recentemente iniziato ad esporre public readonlycampi anche nei miei tipi, perché la creazione di proprietà di sola lettura è semplicemente troppo lavoro per praticamente nessun beneficio.)
stakx - non contribuisce più il

1
@stakx: non è necessario che si concentri su tali tipi; riconoscerli per quello che sono sarebbe sufficiente. La più grande debolezza di C # per quanto riguarda le strutture è il suo più grande problema anche in molte altre aree: il linguaggio fornisce strutture inadeguate per indicare quando determinate trasformazioni sono o non sono appropriate, e la mancanza di tali strutture determina spiacevoli decisioni di progettazione. Ad esempio, il 99% di "strutture mutabili sono cattive" deriva dalla trasformazione MyListOfPoint[3].Offset(2,3);in compilatore var temp=MyListOfPoint[3]; temp.Offset(2,3);, una trasformazione che è falsa quando viene applicata ...
supercat

... al Offsetmetodo. Il modo corretto di prevenire tale codice fasullo non dovrebbe essere quello di rendere inutilmente immutabili le strutture, ma piuttosto di consentire a metodi come Offsetquello di essere taggati con un attributo che proibisce la suddetta trasformazione. Anche le conversioni numeriche implicite avrebbero potuto essere molto migliori se fossero state taggate in modo da essere applicabili solo nei casi in cui la loro invocazione sarebbe stata ovvia. Se esistono sovraccarichi per foo(float,float)e foo(double,double), direi che il tentativo di utilizzare a floate a doublespesso non dovrebbe applicare una conversione implicita, ma dovrebbe invece essere un errore.
supercat

L'assegnazione diretta di un doublevalore a floato il passaggio a un metodo che può accettare un floatargomento ma non doublefarebbe quasi sempre ciò che il programmatore intendeva. Al contrario, assegnare floatun'espressione doublesenza un typecast esplicito è spesso un errore. L'unica volta che consentirebbe la double->floatconversione implicita causerebbe problemi quando si selezionerebbe un sovraccarico non ideale. Suppongo che il modo giusto per prevenire ciò non avrebbe dovuto vietare implcit double-> float, ma taggare i sovraccarichi con attributi per impedire la conversione.
supercat

8

Una classe è un tipo di riferimento. Quando viene creato un oggetto della classe, la variabile a cui è assegnato l'oggetto contiene solo un riferimento a quella memoria. Quando il riferimento all'oggetto viene assegnato a una nuova variabile, la nuova variabile si riferisce all'oggetto originale. Le modifiche apportate tramite una variabile si riflettono nell'altra variabile perché si riferiscono entrambi agli stessi dati. Una struttura è un tipo di valore. Quando viene creata una struttura, la variabile a cui è assegnata la struttura contiene i dati effettivi della struttura. Quando la struttura viene assegnata a una nuova variabile, viene copiata. La nuova variabile e la variabile originale contengono quindi due copie separate degli stessi dati. Le modifiche apportate a una copia non influiscono sull'altra copia. In generale, le classi vengono utilizzate per modellare comportamenti più complessi o dati che si intende modificare dopo la creazione di un oggetto di classe.

Classi e strutture (Guida per programmatori C #)


Le strutture sono anche molto buone nei casi in cui è necessario fissare alcune variabili correlate ma indipendenti insieme al nastro adesivo (ad esempio le coordinate di un punto). Le linee guida MSDN sono ragionevoli se si cerca di produrre strutture che si comportano come oggetti, ma sono molto meno appropriate quando si progettano aggregati; alcuni di loro hanno quasi esattamente torto in quest'ultima situazione. Ad esempio, maggiore è il grado di indipendenza delle variabili incapsulate da un tipo, maggiore è il vantaggio di utilizzare una struttura di campo esposto piuttosto che una classe immutabile.
supercat,

6

MITO # 1: LE STRUTTURE SONO LE CLASSI LEGGEREZZA

Questo mito si presenta in una varietà di forme. Alcune persone credono che i tipi di valore non possano o non debbano avere metodi o altri comportamenti significativi: dovrebbero essere usati come semplici tipi di trasferimento di dati, con solo campi pubblici o proprietà semplici. Il tipo DateTime è un buon controesempio a questo: ha senso che sia un tipo di valore, in termini di essere un'unità fondamentale come un numero o un carattere, e ha anche senso che sia in grado di eseguire calcoli basati su il suo valore. Osservando le cose dall'altra direzione, i tipi di trasferimento dei dati dovrebbero spesso essere comunque dei tipi di riferimento: la decisione dovrebbe essere basata sul valore desiderato o sulla semantica del tipo di riferimento, non sulla semplicità del tipo. Altre persone credono che i tipi di valore siano "più leggeri" dei tipi di riferimento in termini di prestazioni. La verità è che in alcuni casi i tipi di valore sono più performanti: non richiedono la garbage collection a meno che non siano inscatolati, non abbiano l'identificazione del tipo in testa e non richiedano il dereferenziamento, per esempio. Ma in altri modi, i tipi di riferimento sono più performanti: il passaggio dei parametri, l'assegnazione di valori alle variabili, i valori di ritorno e operazioni simili richiedono solo 4 o 8 byte per essere copiati (a seconda che si stia eseguendo il CLR a 32 o 64 bit ) anziché copiare tutti i dati. Immagina se ArrayList fosse in qualche modo un tipo di valore "puro" e passare un'espressione ArrayList a un metodo implicava la copia di tutti i suoi dati! In quasi tutti i casi, le prestazioni non sono comunque determinate da questo tipo di decisione. I colli di bottiglia non sono quasi mai dove pensi che saranno, e prima di prendere una decisione di progettazione basata sulle prestazioni, dovresti misurare le diverse opzioni. Vale la pena notare che la combinazione delle due credenze non funziona neanche. Non importa quanti metodi abbia un tipo (che sia una classe o una struttura): la memoria presa per istanza non è interessata. (C'è un costo in termini di memoria occupata per il codice stesso, ma è sostenuta una volta anziché per ogni istanza.)

MITO # 2: TIPI DI RIFERIMENTO IN DIRETTA SUL CUORE; TIPI DI VALORE IN DIRETTA SULLE PILE

Questo è spesso causato dalla pigrizia da parte della persona che lo ripete. La prima parte è corretta: un'istanza di un tipo di riferimento viene sempre creata sull'heap. È la seconda parte che causa problemi. Come ho già notato, il valore di una variabile vive ovunque sia dichiarato, quindi se hai una classe con una variabile di istanza di tipo int, il valore di quella variabile per un dato oggetto sarà sempre dove si trova il resto dei dati per l'oggetto: sul mucchio. Solo le variabili locali (variabili dichiarate nei metodi) e i parametri del metodo vivono nello stack. In C # 2 e versioni successive, anche alcune variabili locali non vivono realmente nello stack, come vedrai quando esamineremo metodi anonimi nel capitolo 5. QUESTI CONCETTI SONO RILEVANTI ORA? È discutibile che se stai scrivendo un codice gestito, dovresti lasciare che il runtime si preoccupi del modo in cui la memoria viene utilizzata al meglio. Infatti, le specifiche del linguaggio non forniscono garanzie su ciò che vive dove; un runtime futuro potrebbe essere in grado di creare alcuni oggetti nello stack se sa che può cavarsela o il compilatore C # potrebbe generare codice che difficilmente utilizza lo stack. Il prossimo mito è di solito solo una questione terminologica.

MITO # 3: GLI OGGETTI SONO PASSATI PER RIFERIMENTO IN C # DA DEFAULT

Questo è probabilmente il mito più diffuso. Ancora una volta, le persone che fanno questa affermazione spesso (anche se non sempre) sanno come si comporta effettivamente C #, ma non sanno cosa significhi realmente "passare per riferimento". Sfortunatamente, questo è fonte di confusione per le persone che sanno cosa significa. La definizione formale di pass by reference è relativamente complicata, coinvolgendo i valori l e una terminologia informatica simile, ma l'importante è che se si passa una variabile per riferimento, il metodo che si sta chiamando può cambiare il valore della variabile del chiamante modificando il valore del parametro. Ora, ricorda che il valore di una variabile del tipo di riferimento è il riferimento, non l'oggetto stesso. È possibile modificare il contenuto dell'oggetto a cui fa riferimento un parametro senza che il parametro stesso venga passato per riferimento. Per esempio,

void AppendHello(StringBuilder builder)
{
    builder.Append("hello");
}

Quando viene chiamato questo metodo, il valore del parametro (un riferimento a StringBuilder) viene passato per valore. Se dovessi cambiare il valore della variabile del builder all'interno del metodo — per esempio, con l'istruzione builder = null; — quel cambiamento non verrebbe visto dal chiamante, contrariamente al mito. È interessante notare che non solo il bit "per riferimento" del mito è impreciso, ma lo è anche il bit "oggetti passati". Gli oggetti stessi non vengono mai passati, né per riferimento né per valore. Quando è coinvolto un tipo di riferimento, la variabile viene passata per riferimento o il valore dell'argomento (il riferimento) viene passato per valore. A parte qualsiasi altra cosa, questo risponde alla domanda su cosa succede quando null viene usato come argomento per valore-se gli oggetti vengono passati in giro, ciò causerebbe problemi, poiché non ci sarebbe un oggetto da passare! Anziché, il riferimento null viene passato per valore allo stesso modo di qualsiasi altro riferimento. Se questa breve spiegazione ti ha lasciato perplesso, potresti voler guardare il mio articolo, "Parametro che passa in C #" (http://mng.bz/otVt ), che fornisce ulteriori dettagli. Questi miti non sono i soli in circolazione. Pugilato e unboxing arrivano per la loro giusta dose di incomprensioni, che proverò a chiarire in seguito.

Riferimento: C # in Depth 3rd Edition di Jon Skeet


1
Molto bene supponendo che tu abbia ragione. Ottimo anche per aggiungere un riferimento.
NoChance,

5

Penso che una buona prima approssimazione sia "mai".

Penso che una buona seconda approssimazione sia "mai".

Se sei alla disperata ricerca di perf, considerali, ma poi misura sempre.


24
Non sarei d'accordo con quella risposta. Le strutture hanno un uso legittimo in molti scenari. Ecco un esempio: il marshalling dei dati attraversa i processi in modo atomico.
Franci Penov,

25
Dovresti modificare il tuo post ed elaborare i tuoi punti - hai dato la tua opinione, ma dovresti sostenerlo con il motivo per cui prendi questa opinione.
Erik Forbes,

4
Penso che abbiano bisogno di un equivalente della carta Totin 'Chip ( en.wikipedia.org/wiki/Totin%27_Chip ) per usare le strutture. Sul serio.
Greg,

4
In che modo una persona di 87,5 K pubblica una risposta come questa? Lo ha fatto da bambino?
Rohit Vipin Mathews,

3
@Rohit - era sei anni fa; gli standard del sito erano molto diversi allora. questa è ancora una cattiva risposta, però, hai ragione.
Andrew Arnold,

5

Avevo solo a che fare con la pipa denominata Windows Communication Foundation [WCF] e ho notato che ha senso usare Struct per garantire che lo scambio di dati sia di tipo valore anziché di tipo di riferimento .


1
Questo è il miglior indizio di tutti, IMHO.
Ivan

4

C # struct è un'alternativa leggera a una classe. Può fare quasi lo stesso di una classe, ma è meno "costoso" usare una struttura piuttosto che una classe. Il motivo di ciò è un po 'tecnico, ma per riassumere, le nuove istanze di una classe vengono posizionate nell'heap, dove le strutture di nuova istanza vengono posizionate nello stack. Inoltre, non hai a che fare con riferimenti a strutture, come con le classi, ma lavori direttamente con l'istanza struct. Ciò significa anche che quando si passa una struttura a una funzione, è per valore, anziché come riferimento. C'è di più al riguardo nel capitolo sui parametri delle funzioni.

Quindi, dovresti usare le strutture quando desideri rappresentare strutture di dati più semplici, e specialmente se sai che ne creerai un'istanza. Esistono molti esempi nel framework .NET, in cui Microsoft ha utilizzato le strutture anziché le classi, ad esempio la struttura Point, Rectangle e Color.



3

I tipi di struttura o valore possono essere utilizzati nei seguenti scenari:

  1. Se si desidera impedire che l'oggetto venga raccolto dalla garbage collection.
  2. Se è un tipo semplice e nessuna funzione membro modifica i suoi campi di istanza
  3. Se non è necessario derivare da altri tipi o essere derivati ​​da altri tipi.

Puoi saperne di più sui tipi di valore e sui tipi di valori qui su questo link


3

In breve, usa struct se:

1- non è necessario modificare le proprietà / i campi dell'oggetto. Voglio dire, vuoi solo dare loro un valore iniziale e poi leggerli.

2- proprietà e campi nel tuo oggetto sono di tipo valore e non sono così grandi.

In tal caso, puoi sfruttare le strutture per prestazioni migliori e allocazione della memoria ottimizzata poiché utilizzano solo stack anziché stack e heap (in classe)


2

Uso raramente uno struct per le cose. Ma sono solo io. Dipende se ho bisogno che l'oggetto sia nullable o no.

Come indicato in altre risposte, utilizzo le classi per oggetti del mondo reale. Ho anche la mentalità di strutture utilizzate per la memorizzazione di piccole quantità di dati.


-11

Le strutture sono per lo più come classi / oggetti. La struttura può contenere funzioni, membri e può essere ereditata. Ma le strutture sono in C # utilizzate solo per la conservazione dei dati . Le strutture richiedono meno RAM delle classi e sono più facili da raccogliere per Garbage Collector . Ma quando usi le funzioni nella tua struttura, il compilatore prende effettivamente quella struttura in modo molto simile a classe / oggetto, quindi se vuoi qualcosa con funzioni, allora usa classe / oggetto .


2
Le strutture non può essere ereditata, vedi msdn.microsoft.com/en-us/library/0taef578.aspx
HimBromBeere
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.