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.
- 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.
- 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à.