Domanda

Ho una classe C# con a Dispose funzione tramite IDisposable.È destinato ad essere utilizzato all'interno di a using bloccare in modo che la costosa risorsa che gestisce possa essere rilasciata immediatamente.

Il problema è che si è verificato un bug quando in precedenza era stata lanciata un'eccezione Dispose è stato chiamato e il programmatore ha trascurato di utilizzarlo using O finally.

In C++, non ho mai dovuto preoccuparmi di questo.La chiamata al distruttore di una classe verrebbe automaticamente inserita alla fine dell'ambito dell'oggetto.L'unico modo per evitare che ciò accada sarebbe usare l'operatore new e tenere l'oggetto dietro un puntatore, ma ciò richiederebbe lavoro extra per il programmatore non è qualcosa che farebbero per sbaglio, come dimenticare di usare using.

Esiste un modo per a using blocco da utilizzare automaticamente in C#?

Grazie molto.

AGGIORNAMENTO:

Vorrei spiegare perché non accetto le risposte del finalizzatore.Queste risposte sono tecnicamente corrette di per sé, ma non sono distruttori in stile C++.

Ecco il bug che ho riscontrato, ridotto all'essenziale...

try
{
    PleaseDisposeMe a = new PleaseDisposeMe();
    throw new Exception();
    a.Dispose();
}
catch (Exception ex)
{
    Log(ex);
}

// This next call will throw a time-out exception unless the GC
// runs a.Dispose in time.
PleaseDisposeMe b = new PleaseDisposeMe();

Utilizzando FXCop è un suggerimento eccellente, ma se questa è la mia unica risposta, la mia domanda dovrebbe diventare un appello agli utenti di C# o utilizzare C++.Venti dichiarazioni nidificate usando qualcuno?

È stato utile?

Soluzione

Sfortunatamente non c'è modo di farlo direttamente nel codice.Se questo è un problema interno, esistono varie soluzioni di analisi del codice che potrebbero rilevare questo tipo di problemi.Hai dato un'occhiata a FxCop?Penso che questo rileverà queste situazioni e in tutti i casi in cui gli oggetti IDisposable potrebbero essere lasciati in sospeso.Se si tratta di un componente che le persone utilizzano al di fuori della tua organizzazione e non puoi richiedere FxCop, la documentazione è davvero la tua unica risorsa :).

Modificare:Nel caso dei finalizzatori, ciò non garantisce realmente quando avverrà la finalizzazione.Quindi questa potrebbe essere una soluzione per te, ma dipende dalla situazione.

Altri suggerimenti

Dove lavoro usiamo le seguenti linee guida:

  • Ogni classe IDisposable dovere avere un finalizzatore
  • Ogni volta che si utilizza un oggetto IDisposable, è necessario utilizzarlo all'interno di un blocco "using".L'unica eccezione è se l'oggetto è un membro di un'altra classe, nel qual caso la classe che lo contiene deve essere IDisposable e deve chiamare il metodo "Dispose" del membro nella propria implementazione di "Dispose".Ciò significa che "Dispose" non dovrebbe mai essere chiamato dallo sviluppatore se non all'interno di un altro metodo "Dispose", eliminando il bug descritto nella domanda.
  • Il codice in ciascun finalizzatore deve iniziare con un registro di avvisi/errori che ci informa che il finalizzatore è stato chiamato.In questo modo hai ottime possibilità di individuare i bug descritti sopra prima di rilasciare il codice, inoltre potrebbe essere un suggerimento per i bug che si verificano nel tuo sistema.

Per semplificarci la vita, abbiamo anche un metodo SafeDispose nella nostra infrastruttura, che chiama il metodo Dispose del suo argomento all'interno di un blocco try-catch (con registrazione degli errori), per ogni evenienza (anche se i metodi Dispose non dovrebbero lanciare eccezioni ).

Guarda anche: Chris Lionedi riguardo a IDisposable

Modificare:@Litigioso:Una cosa che dovresti fare è chiamare GC.SuppressFinalize all'interno di 'Dispose', in modo che se l'oggetto fosse eliminato, non verrebbe "ridisposto".

Di solito è anche consigliabile tenere una bandierina che indichi se l'oggetto è già stato smaltito o meno.Il modello seguente è solitamente abbastanza buono:

class MyDisposable: IDisposable {
    public void Dispose() {
        lock(this) {
            if (disposed) {
                return;
            }

            disposed = true;
        }

        GC.SuppressFinalize(this);

        // Do actual disposing here ...
    }

    private bool disposed = false;
}

Naturalmente, il blocco non è sempre necessario, ma se non sei sicuro se la tua classe verrà utilizzata o meno in un ambiente multi-thread, è consigliabile mantenerlo.

@Litigioso

If verrà chiamato quando l'oggetto viene spostato fuori dall'ambito e viene riordinato dal Garbage Collector.

Questa affermazione è fuorviante e come l'ho letta non è corretto:Non esiste assolutamente alcuna garanzia su quando verrà chiamato il finalizzatore.Hai assolutamente ragione nel dire che billpg dovrebbe implementare un finalizzatore;tuttavia non verrà chiamato automaticamente quando l'oggetto esce dall'ambito come desidera. Prova, il primo punto elenco sotto Le operazioni di finalizzazione presentano le seguenti limitazioni.

Infatti Microsoft ha concesso un finanziamento a Chris Sells per creare un'implementazione di .NET che utilizzasse il conteggio dei riferimenti invece della garbage collection Collegamento.Come si è scoperto, c'era un considerevole colpo di prestazione.

~ClassName()
{
}

EDIT (grassetto):

If verrà chiamato quando l'oggetto viene spostato fuori dall'ambito e viene riordinato dal Garbage Collector tuttavia, ciò non è deterministico e non è garantito che si verifichi in un momento particolare.Questo è chiamato finalizzatore.Tutti gli oggetti con un finalizzatore vengono inseriti in una coda di finalizzazione speciale dal garbage collector su cui viene richiamato il metodo finalize (quindi tecnicamente è un problema di prestazioni dichiarare finalizzatori vuoti).

Il modello di smaltimento "accettato" secondo le Linee guida quadro è il seguente con le risorse non gestite:

    public class DisposableFinalisableClass : IDisposable
    {
        ~DisposableFinalisableClass()
        {
            Dispose(false);
        }

        public void Dispose()
        {
            Dispose(true);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                // tidy managed resources
            }

            // tidy unmanaged resources
        }
    }

Quindi quanto sopra significa che se qualcuno chiama Dispose le risorse non gestite vengono riordinate.Tuttavia, nel caso in cui qualcuno dimentichi di chiamare Dispose o un'eccezione che impedisca di chiamare Dispose, le risorse non gestite verranno comunque riordinate, solo leggermente più tardi quando il GC le mette le mani sporche (che include la chiusura o la chiusura inaspettata dell'applicazione ).

La procedura migliore è utilizzare un finalizzatore nella classe e utilizzarlo sempre using blocchi.

Tuttavia non esiste un vero equivalente diretto, i finalizzatori sembrano distruttori C, ma si comportano diversamente.

Dovresti nidificare using blocchi, ecco perché il layout del codice C# per impostazione predefinita li inserisce sulla stessa riga...

using (SqlConnection con = new SqlConnection("DB con str") )
using (SqlCommand com = new SqlCommand( con, "sql query") )
{
    //now code is indented one level
    //technically we're nested twice
}

Quando non lo usi using puoi comunque fare quello che fa sotto il cofano:

PleaseDisposeMe a;
try
{
    a = new PleaseDisposeMe();
    throw new Exception();
}
catch (Exception ex) { Log(ex); }  
finally {    
    //this always executes, even with the exception
    a.Dispose(); 
}

Con il codice gestito, C# è molto bravo a prendersi cura della propria memoria, anche quando le cose sono mal smaltite.Se hai a che fare molto con risorse non gestite, non è così forte.

Questo non è diverso da un programmatore che dimentica di usarlo eliminare in C++, tranne per il fatto che almeno qui il garbage collector alla fine riuscirà comunque a raggiungerlo.

E non avrai mai bisogno di utilizzare IDisposable se l'unica risorsa di cui sei preoccupato è la memoria.Il framework lo gestirà da solo.IDisposable è solo per risorse non gestite come connessioni di database, flussi di file, socket e simili.

Una progettazione migliore consiste nel fare in modo che questa classe rilasci da sola la risorsa costosa, prima che venga smaltita.

Ad esempio, se si tratta di una connessione al database, connettiti solo quando necessario e rilascia immediatamente, molto prima che la classe effettiva venga eliminata.

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