Conversione cieca di strutture in classi per nascondere il costruttore predefinito?
-
07-07-2019 - |
Domanda
Ho letto tutte le domande relative a questo argomento e forniscono tutte le ragioni per cui un costruttore predefinito su struct
non è disponibile in C #, ma non ho ancora trovato nessuno che suggerisca una linea d'azione generale di fronte questa situazione.
La soluzione ovvia è semplicemente convertire class
in IsValid
e gestirne le conseguenze.
Esistono altre opzioni per mantenerlo come internal
?
Mi sono imbattuto in questa situazione con uno dei nostri oggetti API di commercio interno. Il designer lo ha convertito da <=> a <=> e ora il costruttore predefinito (che prima era privato) lascia l'oggetto in uno stato non valido.
Ho pensato che se vogliamo mantenere l'oggetto come <=>, dovrebbe essere introdotto un meccanismo per verificare la validità dello stato (qualcosa come una proprietà <=>). Ho incontrato molta resistenza e una spiegazione di & Quot; chiunque usi l'API non dovrebbe usare il costruttore predefinito, & Quot; un commento che certamente ha sollevato le mie sopracciglia. (Nota: l'oggetto in questione è costruito & Quot; correttamente & Quot; tramite metodi statici di fabbrica e tutti gli altri costruttori sono <=>.)
Tutti stanno semplicemente convertendo le loro <=> s in <=> es in questa situazione senza pensarci due volte?
Modifica: vorrei vedere alcuni suggerimenti su come mantenere questo tipo di oggetto come <=> - l'oggetto in questione sopra è molto più adatto come <=> che come <=>.
Soluzione
Per un struct
, si progetta il tipo in modo che l'istanza costruita predefinita (campi tutti zero) sia uno stato valido. Non [ non ] utilizzare arbitrariamente class
invece di <=> senza una buona ragione: non c'è niente di sbagliato nell'usare un tipo di riferimento immutabile.
I miei suggerimenti:
- Assicurati che il motivo dell'uso di <=> sia valido (un profiler [reale] ha rivelato significativi problemi di prestazioni derivanti dalla forte allocazione di un oggetto molto leggero).
- Progetta il tipo in modo che l'istanza costruita predefinita sia valida.
- Se il design del tipo è dettato da vincoli di interoperabilità nativi / COM, avvolgere la funzionalità e non esporre <=> all'esterno del wrapper (tipo annidato privato). In questo modo è possibile documentare e verificare facilmente l'uso corretto dei requisiti di tipo vincolato.
Altri suggerimenti
La ragione di ciò è che una struttura (un'istanza di System.ValueType) è trattata appositamente dal CLR: è inizializzata con tutti i campi pari a 0 (o predefiniti). Non è nemmeno necessario crearne uno, basta dichiararlo. Ecco perché sono richiesti i costruttori predefiniti.
Puoi aggirare il problema in due modi:
- Crea una proprietà come IsValid per indicare se è una struttura valida, come indichi e
- in .Net 2.0 considera l'utilizzo di Nullable < T > per consentire una struttura non inizializzata (null).
Cambiare la struttura in una classe può avere alcune conseguenze molto sottili (in termini di utilizzo della memoria e identità dell'oggetto che emergono di più in un ambiente multithread) e NullReferenceExceptions non così sottile ma difficile da debug per oggetti non inizializzati.
/ p>
Il motivo per cui non è possibile definire un costruttore predefinito è illustrato dalla seguente espressione:
new MyStruct[1000];
Hai 3 opzioni qui
- chiamando il costruttore predefinito 1000 volte o
- creazione di dati corrotti (si noti che una struttura può contenere riferimenti; se non si inizializza o si cancella il riferimento, è possibile accedere potenzialmente alla memoria arbitraria) oppure
- cancella la memoria allocata con zero (a livello di byte).
.NET fa lo stesso sia per le strutture che per le classi: i campi e gli elementi dell'array sono oscurati con zero. Ciò comporta anche un comportamento più coerente tra strutture e classi e nessun codice non sicuro. Inoltre, consente al framework .NET di non specializzare qualcosa come new byte[1000]
.
E questo è il costruttore predefinito per le strutture .NET richiede e si prende cura di se stesso: azzera tutti i byte.
Ora, per gestirlo, hai un paio di opzioni:
- Aggiungi una proprietà Am-I-Initialized alla struttura (come
HasValue
suNullable
). - Consenti alla struttura azzerata di essere un valore valido (come 0 è un valore valido per un decimale).