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

È stato utile?

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:

  1. 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.
  2. 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) .
  3. 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.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top