Quando usi una struct al posto di una classe? [chiuso]

160

Quali sono le tue regole pratiche per quando usare structs vs. classes? Sto pensando alla definizione C # di quei termini, ma se la tua lingua ha concetti simili mi piacerebbe sentire anche la tua opinione.

Tendo ad usare le classi per quasi tutto, e uso le structs solo quando qualcosa è molto semplicistico e dovrebbe essere un tipo di valore, come un PhoneNumber o qualcosa del genere. Ma questo sembra un uso relativamente minore e spero che ci siano casi d'uso più interessanti.

    
posta RationalGeek 12.07.2011 - 18:22
fonte

5 risposte

150

La regola generale da seguire è che le strutture dovrebbero essere raccolte di piccole dimensioni, semplici (di un livello) di proprietà correlate, che sono immutabili una volta create; per qualsiasi altra cosa, usa una classe.

C # è bello in quanto le strutture e le classi non hanno differenze esplicite nella dichiarazione diverse dalla parola chiave che definisce; quindi, se ritieni di dover "aggiornare" una struttura in una classe o, al contrario, "ridimensionare" una classe in una struttura, è principalmente una semplice questione di cambiare la parola chiave (ci sono alcuni altri trucchi, le strutture non possono derivare da qualsiasi altra classe o tipo di struct e non possono definire esplicitamente un costruttore senza parametri predefinito.

Dico "principalmente", perché la cosa più importante da sapere sulle strutture è che, poiché sono tipi di valore, trattarli come classi (tipi di riferimento) possono finire per un dolore e mezzo. In particolare, rendere mutabili le proprietà di una struttura può causare comportamenti imprevisti.

Ad esempio, supponiamo di avere una classe SimpleClass con due proprietà, A e B. Si crea un'istanza di una copia di questa classe, si inizializzano A e B, quindi si passa l'istanza a un altro metodo. Questo metodo modifica ulteriormente A e B. Indietro nella funzione chiamante (quella che ha creato l'istanza), i tuoi A e B della tua istanza avranno i valori dati loro dal metodo chiamato.

Ora, lo fai diventare una struttura. Le proprietà sono ancora mutevoli. Esegui le stesse operazioni con la stessa sintassi di prima, ma ora i nuovi valori di A e B non si trovano nell'istanza dopo aver chiamato il metodo. Quello che è successo? Bene, la tua classe ora è una struttura, il che significa che è un tipo di valore. Se si passa un tipo di valore a un metodo, il valore predefinito (senza una parola chiave out o ref) deve passare "in base al valore"; una copia superficiale dell'istanza viene creata per essere utilizzata dal metodo e quindi distrutta quando il metodo viene eseguito lasciando intatta l'istanza iniziale.

Questo diventa ancora più confuso se si dovesse avere un tipo di riferimento come membro della propria struct (non vietato, ma estremamente cattiva pratica in quasi tutti i casi); la classe non sarebbe clonata (solo il riferimento della struct ad essa), quindi le modifiche alla struct non influenzerebbero l'oggetto originale, ma le modifiche alla sottoclasse della struct influenzeranno l'istanza dal codice chiamante. Questo può facilmente mettere le strutture mutabili in stati molto incoerenti che possono causare errori molto lontani da dove si trova il vero problema.

Per questo motivo, praticamente ogni autorità su C # dice di rendere sempre immutabili le tue strutture; consentire al consumatore di specificare i valori delle proprietà solo sulla costruzione di un oggetto e non fornire mai alcun mezzo per modificare i valori di tale istanza. I campi di sola lettura o proprietà get-only sono la regola. Se il consumatore desidera modificare il valore, può creare un nuovo oggetto basato sui valori di quello vecchio, con le modifiche che desidera, oppure può chiamare un metodo che farà lo stesso. Ciò li costringe a trattare una singola istanza della tua struttura come un "valore" concettuale, indivisibile e distinto da (ma possibilmente equabile a) tutti gli altri. Se eseguono un'operazione su un "valore" memorizzato dal tuo tipo, ottengono un nuovo "valore" diverso dal loro valore iniziale, ma comunque comparabile e / o semanticamente equabile.

Per un buon esempio, guarda il tipo DateTime. Non è possibile assegnare direttamente alcun campo di un'istanza DateTime; è necessario crearne uno nuovo o chiamare un metodo su quello esistente che produrrà una nuova istanza. Questo perché data e ora sono un "valore", come il numero 5, e una modifica al numero 5 produce un nuovo valore che non è 5. Solo perché 5 + 1 = 6 non significa che 5 è ora 6 perché hai aggiunto 1 ad esso. DateTimes funzionano allo stesso modo; 12:00 non "diventa" 12:01 se aggiungi un minuto, invece ottieni un nuovo valore 12:01 che è distinto da 12:00. Se questo è uno stato logico del tuo tipo (esempi concreti buoni non incorporati in .NET sono Money, Distance, Weight e altre quantità di un UOM in cui le operazioni devono prendere in considerazione tutte le parti del valore), quindi utilizzare una struttura e progettarla di conseguenza. Nella maggior parte degli altri casi in cui gli oggetti secondari di un oggetto dovrebbero essere mutabili in modo indipendente, utilizzare una classe.

    
risposta data 12.07.2011 - 19:16
fonte
11

La risposta: "Usa una struttura per i costrutti di dati puri e una classe per gli oggetti con operazioni" è sicuramente IMO errata. Se una struttura contiene un gran numero di proprietà, allora una classe è quasi sempre più appropriata. Microsoft dice spesso, dal punto di vista dell'efficienza, se il tuo tipo è più grande di 16 byte dovrebbe essere una classe.

link

    
risposta data 11.12.2012 - 11:20
fonte
9

La differenza più importante tra class e struct è ciò che accade nella seguente situazione:

  Thing thing1 = new Thing();
  thing1.somePropertyOrField = 5;
  Thing thing2 = thing1;
  thing2.somePropertyOrField = 9;

Quale dovrebbe essere l'effetto dell'ultima affermazione su thing1.somePropertyOrField ? Se Thing una struct e somePropertyOrField è un campo pubblico esposto, gli oggetti thing1 e thing2 saranno "distaccati" l'uno dall'altro, quindi l'ultima istruzione non influirà su thing1 . Se Thing è una classe, quindi thing1 e thing2 verranno collegati tra loro, quindi l'ultima istruzione scriverà thing1.somePropertyOrField . Si dovrebbe usare una struttura nei casi in cui la precedente semantica avrebbe più senso, e dovrebbe usare una classe nei casi in cui la seconda semantica avrebbe più senso.

Si noti che mentre alcune persone consigliano che il desiderio di creare qualcosa di mutevole è un argomento a favore del fatto che sia la classe, suggerirei il contrario è vero: se qualcosa che esiste allo scopo di contenere alcuni dati sarà mutabile, e se non sarà ovvio se le istanze sono collegate a qualcosa, la cosa dovrebbe essere una struct (probabilmente con i campi esposti) in modo da chiarire che le istanze non sono collegate ad altro.

Ad esempio, considera le istruzioni:

  Person somePerson = myPeople.GetPerson("123-45-6789");
  somePerson.Name = "Mary Johnson"; // Had been "Mary Smith"

La seconda affermazione altererà le informazioni memorizzate in myPeople ? Se Person è una struttura di campo esposto, non lo farà, e il fatto che non sarà una conseguenza ovvia della sua essere una struttura di campo esposto ; se Person è una struct e si desidera aggiornare myPeople , si dovrebbe chiaramente fare qualcosa come myPeople.UpdatePerson("123-45-6789", somePerson) . Se Person è una classe, tuttavia, potrebbe essere molto più difficile determinare se il codice precedente non aggiornerebbe mai il contenuto di MyPeople , aggiornarlo sempre o talvolta aggiornarlo.

Riguardo alla nozione che le strutture dovrebbero essere "immutabili", non sono d'accordo in generale. Esistono casi di utilizzo validi per le strutture "immutabili" (in cui gli invarianti sono applicati in un costruttore) ma che richiedono la riscrittura di un'intera struttura ogni volta che una parte di essa cambia, è scomoda, dispendiosa e più adatta a causare bug rispetto alla semplice esposizione campi direttamente. Ad esempio, considera una struttura PhoneNumber i cui campi includono, tra gli altri, AreaCode e Exchange , e supponiamo che uno abbia un List<PhoneNumber> . L'effetto di quanto segue dovrebbe essere abbastanza chiaro:

  for (int i=0; i < myList.Count; i++)
  {
    PhoneNumber theNumber = myList[i];
    if (theNumber.AreaCode == "312")
    {
      string newExchange = "";
      if (new312to708Exchanges.TryGetValue(theNumber.Exchange), out newExchange)
      {
        theNumber.AreaCode = "708";
        theNumber.Exchange = newExchange;
        myList[i] = theNumber;
      }
    }
  }

Si noti che nulla nel codice precedente conosce o interessa i campi in PhoneNumber diversi da AreaCode e Exchange . Se PhoneNumber fosse una cosiddetta struttura "immutabile", sarebbe necessario che fornisse un metodo withXX per ogni campo, che restituirebbe una nuova istanza di struttura che conteneva il valore passato nel campo indicato, oppure sarebbe necessario che il codice come sopra sia a conoscenza di ogni campo della struttura. Non esattamente attraente.

A proposito, ci sono almeno due casi in cui le strutture possono contenere ragionevolmente riferimenti a tipi mutabili.

  1. La semantica della struttura indica che sta memorizzando l'identità dell'oggetto in questione, piuttosto che come scorciatoia per contenere le proprietà dell'oggetto. Ad esempio, un 'KeyValuePair' potrebbe contenere le identità di determinati pulsanti, ma non ci si aspetterebbe che contenga informazioni persistenti sulle posizioni di tali pulsanti, sugli stati di evidenziazione, ecc.
  2. La struttura sa che contiene l'unico riferimento a quell'oggetto, nessun altro otterrà un riferimento e tutte le mutazioni che verranno mai eseguite su quell'oggetto saranno state eseguite prima che un riferimento ad esso venga memorizzato ovunque.

Nel primo scenario, l'IDENTITÀ dell'oggetto sarà immutabile; nel secondo, la classe dell'oggetto nidificato potrebbe non applicare l'immutabilità, ma la struttura che contiene il riferimento lo farà.

    
risposta data 06.07.2012 - 23:59
fonte
6

Tendo ad usare le strutture solo quando c'è bisogno di un metodo, rendendo così una classe troppo "pesante". Quindi a volte tratto le strutture come oggetti leggeri. ATTENZIONE: questo non funziona sempre e non è sempre la cosa migliore da fare, quindi dipende molto dalla situazione!

RISORSE EXTRA

Per una spiegazione più formale, MSDN dice: link Questo link verifica ciò che ho menzionato sopra, le strutture dovrebbero essere utilizzate solo per contenere una piccola quantità di dati.

    
risposta data 12.07.2011 - 18:25
fonte
2

Utilizza una struttura per i costrutti di dati puri e una classe per gli oggetti con operazioni.

Se la struttura dei dati non ha bisogno di controllo degli accessi e non ha operazioni speciali oltre a get / set, usa una struct. Ciò rende ovvio che tutta la struttura è un contenitore per i dati.

Se la tua struttura ha operazioni che modificano la struttura in un modo più complesso, usa una classe. Una classe può anche interagire con altre classi tramite argomenti ai metodi.

Consideralo come la differenza tra un mattone e un aereo. Un mattone è una struttura; ha lunghezza, larghezza e altezza. Non può fare molto. Un aereo potrebbe avere più operazioni che lo mutano in qualche modo, come spostare le superfici di controllo e cambiare la spinta del motore. Sarebbe meglio come classe.

    
risposta data 12.07.2011 - 18:49
fonte

Leggi altre domande sui tag