IDisposable senza finalizzatore in uno scenario Singleton

1

È giusto aspettarsi che se una classe C # si occupa di risorse non gestite e implementazioni IDisposable, allora dovrebbe anche implementare qualche tipo di logica finalizzatore?

Abbiamo una classe di utilità fornita dal fornitore che utilizza alcune risorse non gestite. Non abbiamo il codice sorgente, quindi non conosciamo troppi dettagli di implementazione, a parte il fatto che implementa IDisposable ma non ha alcun finalizzatore o SafeHandle. Quindi, senza chiamare Dispose, perde. Codice di esempio semplificato:

public class Utility : IDisposable
{
    //This class has some unmanaged resources
    ...
    //Utility function
    public void DoSomething(...) { ... }
    //IDisposable
    public void Dispose() { ... }
    protected virtual void Dispose(bool disposing) { ... }
}

Nel nostro codice abbiamo creato una facciata attorno a esso per semplificare l'utilizzo e aggiunto anche un singleton dato che è usato in decine di posti nella nostra complessa base di codice legacy. Esempio:

public class UtilityFacade : IDisposable
{
    protected Utility utility = new Utility();
    //Simplified utility function
    public void DoSomethingGood() { utility.DoSomething(1,2,3); }
    //IDisposable
    protected bool disposed = false;
    protected virtual void Dispose(bool disposing)
    {
        if (!disposed)
        {
            if (disposing)
            {
                utility.Dispose();
            }
            disposed = true;
        }
    }
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
    //Singleton
    public static UtilityFacade Instance { get; } = new UtilityFacade();
}

Quindi dalle nostre applicazioni (sito web, servizio web, app console) verrebbe usato in questo modo:

UtilityFacade.Instance.DoSomethingGood();

Il problema è che poiché l'istanza è un membro statico, non verrà mai eliminata tramite IDisposable, quindi la classe Utility non verrà eliminata e, poiché non implementa alcun finalizzatore, perde risorse non gestite alla fine del processo di applicazione.

Quale è la scelta giusta qui:

  1. Il fornitore dice che dovremmo chiamare Utility.Dispose () al termine dell'applicazione. Ciò richiederebbe ulteriore codifica per noi, ad es. collegarsi ad Application_End per applicazioni web, o aggiungere try {} finally {...} blocks per console apps per la pulizia deterministica, o aggiungere un finalizzatore alla classe facade (che manterrebbe il codice correlato all'utilità in un posto almeno) .
  2. Chiediamo al fornitore di implementare il finalizzatore. Dato che la classe Utility tratta direttamente con le risorse non gestite, è necessario aggiungere la logica del finalizzatore. Secondo alcuni articoli sulla rete è la migliore pratica raccomandata da Microsoft.

O qualche altro consiglio?

    
posta Tamás Somogyi 12.07.2018 - 01:55
fonte

4 risposte

4

Un finalizzatore dovrebbe essere implementato per qualsiasi classe che gestisca risorse non gestite; la classe del venditore sarebbe migliorata se lo facessero. Tuttavia, questo non migliorerà necessariamente il tuo codice, perché non è garantito che i finalizzatori vengano chiamati, e in effetti è molto improbabile che i finalizzatori di oggetti raggiungibili tramite statica vengano chiamati.

Quindi la soluzione giusta è chiamare Dispose sulla tua facciata al momento giusto. Il modo migliore per raggiungere questo obiettivo è non renderlo un singleton in primo luogo. Inietti l'istanza nei componenti che ne hanno bisogno e assicurati che l'istanza venga pulita correttamente. Per le app console, questo può significare un semplice blocco using in Main . Per le app Web, probabilmente dovresti già avere un contenitore DI (soprattutto se stai utilizzando ASP.Net Core) che è stato cablato per essere eliminato all'arresto delle applicazioni e che a sua volta eliminerà tutte le istanze non transitori gestite.

    
risposta data 12.07.2018 - 10:07
fonte
1

Dispose dovrebbe essere sufficiente.
Puoi creare una classe di facciata che chiamerà dispose

public class UtilityFacade
{
    public void DoSomethingGood() 
    { 
        using (var utility = new Utility())
        {
            utility.DoSomething(1,2,3);
        }
    }

    // Make it singleton if you "really" need it.
    public static UtilityFacade Instance { get; } = new UtilityFacade();
}
    
risposta data 12.07.2018 - 08:51
fonte
0

it is the best practice recommended by Microsoft.

Bene, non segui le migliori pratiche che Microsoft ha esposto tu stesso. Hai appena detto che seguire le linee guida di base e chiamare sempre .Dispose() è troppo lavoro per te. Quindi chiamare il tuo fornitore e chiedergli di costruire qualcosa che è considerato un guardrail contro il fallimento del client, perché hai intenzione di fallire probabilmente non ti sta facendo sembrare buono. Potrebbero farlo, perché li paghi, ma non contare di avere alcuna reputazione per lo sviluppo di software professionale con loro in qualunque momento presto.

Vuoi seguire le linee guida in un solo punto, quella sarebbe la tua facciata. Se insisti sul fatto che il finalizzatore sia una buona soluzione, scrivine uno per quella classe.

D'altro canto, il tuo problema non è esattamente nuovo o unico. La soluzione che la gente usa è chiamata "contenitore per l'iniezione di dipendenza". I progetti Microsoft ASP.NET Core sono dotati di uno già impostato per te. I progetti più vecchi avranno probabilmente tutorial in rete su come crearne uno. Queste cose gestiscono la durata dell'oggetto. È possibile utilizzare la loro classe di utilità e indicare al contenitore che è necessario creare uno per ogni ambito, ad esempio. Il contenitore chiamerà Dispose() su di esso al termine dell'ambito. Oppure si potrebbe dire al contenitore di averlo come una vita singola (ciò non ha nulla a che fare con il modello singleton (anti-). Ciò significa che l'unico oggetto vivrà finché il contenitore vivrà.

Quindi il mio suggerimento sarebbe quello di mirare alla buona artigianalità e implementare un framework di iniezione delle dipendenze per la vostra soluzione. Allo stesso tempo, non farà male a suggerire al tuo fornitore di implementare un finalizzatore, ma non contare su di esso e non chiedere a ask . È qualcosa che è bello avere, non qualcosa di cui avresti bisogno.

    
risposta data 12.07.2018 - 10:07
fonte
0

Direi che è necessario che il processo dell'applicazione per eseguire la pulizia all'uscita sia errato, indipendentemente da come è codificato. Un processo può essere ucciso senza dare la possibilità di eseguire nulla alla fine. Il sistema non dovrebbe entrare in uno stato non funzionante, indipendentemente dal processo eseguito o meno prima delle sue uscite.

Altrimenti concordo con la risposta di Sebastian Redl, dovresti disporre l'istanza singleton prima dell'uscita, sia normale che errata. Ma tieni a mente che qualunque cosa tu faccia se il processo viene ucciso, qualcosa rimarrà e avrà bisogno di un po 'di pulizia.

    
risposta data 12.07.2018 - 12:06
fonte

Leggi altre domande sui tag