Le strutture con campi o proprietà mutabili pubblici non sono malvagie.
I metodi Struct (distinti dai setter di proprietà) che mutano "questo" sono in qualche modo malvagi, solo perché .net non fornisce un mezzo per distinguerli da metodi che non lo fanno. I metodi Struct che non mutano "questo" dovrebbero essere invocabili anche su strutture di sola lettura senza necessità di copia difensiva. I metodi che mutano "questo" non dovrebbero essere affatto invocabili su strutture di sola lettura. Poiché .net non vuole vietare i metodi di struttura che non modificano "this" dall'essere invocati su strutture di sola lettura, ma non vuole permettere che le strutture di sola lettura vengano mutate, copia difensivamente le strutture in lettura- solo contesti, probabilmente ottenendo il peggio di entrambi i mondi.
Nonostante i problemi con la gestione dei metodi auto-mutanti in contesti di sola lettura, tuttavia, le strutture mutabili offrono spesso una semantica di gran lunga superiore ai tipi di classe mutabili. Considera le seguenti tre firme di metodo:
struct PointyStruct {public int x, y, z;};
class PointyClass {public int x, y, z;};
void Method1 (PointyStruct foo);
void Method2 (ref PointyStruct foo);
void Method3 (PointyClass foo);
Per ciascun metodo, rispondi alle seguenti domande:
- Supponendo che il metodo non utilizzi alcun codice "non sicuro", potrebbe modificare foo?
- Se non esistono riferimenti esterni a "pippo" prima che venga chiamato il metodo, potrebbe esistere un riferimento esterno dopo?
risposte:
Domanda 1::
Method1()
no (intento chiaro)
Method2()
: sì (intento chiaro)
Method3()
: sì (intento incerto)
Domanda 2
Method1()
:: no
Method2()
: no (a meno che non sia sicuro)
Method3()
: sì
Method1 non può modificare foo e non ottiene mai un riferimento. Method2 ottiene un riferimento di breve durata a foo, che può utilizzare per modificare i campi di foo un numero qualsiasi di volte, in qualsiasi ordine, fino a quando non ritorna, ma non può persistere quel riferimento. Prima che il metodo 2 ritorni, a meno che non utilizzi un codice non sicuro, tutte le copie che potrebbero essere state fatte del suo riferimento "foo" saranno scomparse. Method3, a differenza di Method2, ottiene un riferimento promiscuamente condivisibile a foo, e non si può dire cosa potrebbe farci. Potrebbe non cambiare affatto foo, potrebbe cambiare foo e poi tornare, oppure potrebbe dare un riferimento a foo a un altro thread che potrebbe mutarlo in qualche modo arbitrario in un futuro futuro arbitrario.
Le matrici di strutture offrono una meravigliosa semantica. Dato RectArray [500] di tipo Rectangle, è chiaro ed ovvio come ad esempio copiare l'elemento 123 nell'elemento 456 e poi qualche tempo dopo impostare la larghezza dell'elemento 123 su 555, senza disturbare l'elemento 456. "RectArray [432] = RectArray [321 ]; ...; RectArray [123] .Width = 555; ". Sapere che Rectangle è una struttura con un campo intero chiamato Larghezza ti dirà tutto ciò che devi sapere sulle affermazioni di cui sopra.
Supponiamo ora che RectClass fosse una classe con gli stessi campi di Rectangle e si volesse fare le stesse operazioni su un RectClassArray [500] di tipo RectClass. Forse l'array dovrebbe contenere 500 riferimenti immutabili preinizializzati a oggetti RectClass mutabili. in tal caso, il codice corretto sarebbe simile a "RectClassArray [321] .SetBounds (RectClassArray [456]); ...; RectClassArray [321] .X = 555;". Forse si presume che l'array contenga istanze che non cambieranno, quindi il codice corretto sarebbe più simile a "RectClassArray [321] = RectClassArray [456]; ...; RectClassArray [321] = New RectClass (RectClassArray [321 ]); RectClassArray [321] .X = 555; " Per sapere cosa si dovrebbe fare, si dovrebbe sapere molto di più sia su RectClass (ad es. Supporta un costruttore di copie, un metodo di copia da, ecc. ) e l'uso previsto dell'array. Da nessuna parte pulito come usare una struttura.
A dire il vero, sfortunatamente non esiste un buon modo per qualsiasi classe di container diversa da un array di offrire la semantica pulita di un array di strutture. Il migliore che si potrebbe fare, se si volesse indicizzare una raccolta con ad esempio una stringa, sarebbe probabilmente quello di offrire un metodo "ActOnItem" generico che accetterebbe una stringa per l'indice, un parametro generico e un delegato che verrebbe passato per riferimento sia il parametro generico che l'elemento di raccolta. Ciò consentirebbe quasi la stessa semantica degli array di struttura, ma a meno che le persone vb.net e C # non possano essere perseguite per offrire una bella sintassi, il codice apparirà goffo anche se è ragionevolmente prestazioni (passare un parametro generico sarebbe consentire l'uso di un delegato statico ed evitare qualsiasi necessità di creare istanze di classe temporanee).
Personalmente, sono irritato dall'odio Eric Lippert et al. vomitare per quanto riguarda i tipi di valori mutabili. Offrono una semantica molto più pulita rispetto ai promiscui tipi di riferimento utilizzati ovunque. Nonostante alcune limitazioni con il supporto di .net per i tipi di valore, ci sono molti casi in cui i tipi di valori mutabili si adattano meglio di qualsiasi altro tipo di entità.
int
s, sebool
tutti gli altri tipi di valore sono cattivi. Esistono casi di mutabilità e immutabilità. Questi casi dipendono dal ruolo svolto dai dati, non dal tipo di allocazione / condivisione della memoria.