Perché non c'è RAII in .NET?
-
05-07-2019 - |
Domanda
Essendo principalmente uno sviluppatore C ++ l'assenza di RAII (Resource Acquisition Is Initialization) in Java e .NET mi ha sempre infastidito. Il fatto che l'onere della pulizia venga spostato dal class writer al suo consumatore (tramite prova finalmente
o .NET usando
il costrutto ) sembra essere nettamente inferiore.
Capisco perché in Java non esiste supporto per RAII poiché tutti gli oggetti si trovano nell'heap e il garbage collector non supporta intrinsecamente la distruzione deterministica, ma in .NET con l'introduzione di value-types ( struct
) abbiamo il candidato (apparentemente) perfetto per RAII. Un tipo di valore creato nello stack ha un ambito ben definito e può essere utilizzata la semantica del distruttore C ++. Tuttavia, il CLR non consente a un tipo di valore di avere un distruttore.
Le mie ricerche casuali hanno riscontrato un argomento secondo cui se un tipo di valore è inscatolato cade sotto la giurisdizione del garbage collector e quindi la sua distruzione diventa non deterministica. Sento che questo argomento non è abbastanza forte, i vantaggi di RAII sono abbastanza grandi da dire che un tipo di valore con un distruttore non può essere inscatolato (o usato come membro della classe).
Per farla breve, la mia domanda è : ci sono altri motivi per cui non è possibile utilizzare i tipi di valore per introdurre RAII in .NET? (o pensi che le mie argomentazioni sugli ovvi vantaggi di RAII siano errate?)
Modifica: non devo aver formulato chiaramente la domanda poiché le prime quattro risposte hanno perso il punto. Conosco di su Finalize
e le sue caratteristiche non deterministiche, conosco il costrutto using
e penso che queste due opzioni siano inferiori a RAII. using
è un'altra cosa che il consumatore di una classe deve ricordare (quante persone hanno dimenticato di inserire un StreamReader
in un blocco using
?). La mia domanda è filosofica sul design del linguaggio, perché è così e può essere migliorato?
Ad esempio con un tipo di valore generico deterministicamente distruttibile, posso rendere ridondanti le parole chiave utilizzando
e lock
(ottenibili con le classi di libreria):
public struct Disposer<T> where T : IDisposable
{
T val;
public Disposer(T t) { val = t; }
public T Value { get { return val; } }
~Disposer() // Currently illegal
{
if (val != default(T))
val.Dispose();
}
}
Non posso fare a meno di concludere con una citazione a proposito che una volta ho visto ma non riesco a trovare la sua origine
Puoi prendere la mia distruzione deterministica quando la mia fredda mano morta va fuori campo. - Anon
Soluzione
Un titolo migliore sarebbe "Perché non c'è RAII in C # / VB". C ++ / CLI (L'evoluzione dell'aborto che era Managed C ++) ha RAII nello stesso identico senso del C ++. È tutto solo zucchero di sintassi per lo stesso modello di finalizzazione utilizzato dal resto dei linguaggi della CLI (i distruttori negli oggetti gestiti per C ++ / CLI sono effettivamente finalizzatori), ma è lì.
Potrebbe piacerti http://blogs.msdn.com /hsutter/archive/2004/07/31/203137.aspx
Altri suggerimenti
Ottima domanda e una che mi ha disturbato molto. Sembra che i benefici di RAII siano percepiti in modo molto diverso. Nella mia esperienza con .NET, la mancanza di una raccolta di risorse deterministica (o almeno affidabile) è uno dei maggiori svantaggi. In effetti, .NET mi ha costretto diverse volte a impiegare intere architetture per gestire risorse non gestite che potrebbe (ma potrebbe non) richiedere una raccolta esplicita. Il che, ovviamente, è un grosso svantaggio perché rende l'architettura generale più difficile e distoglie l'attenzione del cliente dagli aspetti più centrali.
Brian Harry ha un bel post sui razionali qui .
Ecco un estratto:
Che dire della finalizzazione deterministica e dei tipi di valore (strutture)?
-------------- Ho visto molte domande sulle strutture che hanno distruttori, ecc. Questo vale commento. Ci sono una varietà di problemi per cui alcune lingue non lo fanno li hanno.
(1) composizione - Non ti danno vita deterministica in generale caso per gli stessi tipi di composizione motivi sopra descritti. Qualunque classe non deterministica contenente una non chiamerebbe il distruttore fino a quando non lo farà è stato comunque finalizzato dal GC.
(2) costruttori di copie - L'unico posto dove sarebbe davvero bello impilare i locali assegnati. Vorrebbero essere mirato al metodo e tutto sarebbe grande. Sfortunatamente, per ottenere questo per funzionare davvero, devi anche farlo aggiungi i costruttori di copie e chiamali ogni volta che viene copiata un'istanza. Questo è uno dei più brutti e più cose complesse su C ++. Alla fine ottenere l'esecuzione del codice in tutto il posto dove non te lo aspetti. esso causa un sacco di problemi linguistici. Alcuni designer linguistici hanno scelto di farlo stai lontano da questo.
Diciamo che abbiamo creato le strutture con distruttori ma ha aggiunto un sacco di restrizioni per rendere il loro comportamento sensibile di fronte ai problemi sopra. Le restrizioni sarebbero qualcosa come:
(1) Puoi dichiararli solo come locali variabili.
(2) Puoi solo passarli by-ref
(3) Non puoi assegnarli, tu può solo accedere ai campi e chiamare metodi su di essi.
(4) Non puoi boxare loro.
(5) Problemi nell'utilizzarli Riflessione (associazione tardiva) perché di solito comporta la boxe.
forse di più, ma è un buon inizio.
A che cosa serviranno queste cose? Voluto in realtà crei un file o un classe di connessione al database che può Utilizzare SOLO come variabile locale? io non credere che qualcuno lo farebbe davvero. Quello che faresti invece è creare a connessione per scopi generici e quindi creare un wrapper auto-distrutto per utilizzare come variabile locale con ambito. Il il chiamante avrebbe scelto quello che loro volevo usare. Si noti che il chiamante ha creato a decisione e non è del tutto incapsulato nell'oggetto stesso. Dato che potresti usare qualcosa come i suggerimenti che emergono in a un paio di sezioni.
Il sostituto di RAII in .NET è il use-pattern, che funziona quasi altrettanto una volta che ci si abitua.
Il più vicino a te è l'operatore stackalloc molto limitato.
Ci sono alcuni thread simili se li cerchi, ma sostanzialmente ciò che si riduce è che se vuoi RAII su .NET implementa semplicemente un tipo IDisposable e usa " usando " dichiarazione per ottenere lo smaltimento deterministico. In questo modo molti degli stessi ideomi possono essere implementati e utilizzati solo in modo leggermente più prolisso.
IMHO, le grandi cose di cui VB.net e C # hanno bisogno sono:
- a " utilizzando " dichiarazione per i campi, che indurrebbe il compilatore a generare codice per disporre di tutti i campi così taggati. Il comportamento predefinito dovrebbe essere per il compilatore di rendere una classe implementare IDisposable in caso contrario, o inserire la logica di eliminazione prima dell'inizio della routine di eliminazione principale per uno dei numerosi modelli di implementazione IDisposal comuni, oppure utilizzare un attributo per specificare che le cose di smaltimento dovrebbero andare in una routine con un nome particolare.
- un mezzo per disporre in modo deterministico di oggetti i cui costruttori e / o inizializzatori di campo generano un'eccezione, sia tramite un comportamento predefinito (chiamando il metodo di smaltimento predefinito) o un comportamento personalizzato (chiamando un metodo con un nome particolare) .
- Per vb.net, un metodo generato automaticamente per annullare tutti i campi WithEvent.
Tutti questi possono essere compilati abbastanza bene in vb.net e un po 'meno bene in C #, ma un supporto di prima classe per loro migliorerebbe entrambe le lingue.
Puoi fare una forma di RAII in .net e java usando i metodi finalize (). Un sovraccarico finalize () viene chiamato prima che la classe venga ripulita dal GC e quindi può essere utilizzata per ripulire qualsiasi risorsa che non dovrebbe assolutamente essere mantenuta dalla classe (mutex, socket, handle di file, ecc.). Tuttavia non è ancora deterministico.
Con .NET puoi fare un po 'di questo in modo deterministico con l'interfaccia IDisposable e la parola chiave using, ma questo ha dei limiti (usare il costrutto quando richiesto richiesto per comportamento deterministico, ancora nessuna deallocazione di memoria deterministica, non usato automaticamente nelle classi, ecc. ).
E sì, penso che ci sia spazio per l'introduzione delle idee RAII in .NET e in altri linguaggi gestiti, sebbene l'esatto meccanismo possa essere discusso all'infinito. L'unica altra alternativa che potrei vedere sarebbe quella di introdurre un GC in grado di gestire la pulizia arbitraria delle risorse (non solo la memoria) ma poi hai problemi quando tali risorse devono essere rilasciate in modo deterministico.