Implementazione di IDisposable correttamente


145

Nelle mie lezioni implemento IDisposable come segue:

public class User : IDisposable
{
    public int id { get; protected set; }
    public string name { get; protected set; }
    public string pass { get; protected set; }

    public User(int UserID)
    {
        id = UserID;
    }
    public User(string Username, string Password)
    {
        name = Username;
        pass = Password;
    }

    // Other functions go here...

    public void Dispose()
    {
        // Clear all property values that maybe have been set
        // when the class was instantiated
        id = 0;
        name = String.Empty;
        pass = String.Empty;
    }
}

In VS2012, la mia analisi del codice dice di implementare IDisposable correttamente, ma non sono sicuro di cosa ho fatto di sbagliato qui.
Il testo esatto è il seguente:

CA1063 Implementare IDisposable correttamente Fornire un'implementazione scavalcabile di Dispose (bool) su 'Utente' o contrassegnare il tipo come sigillato. Una chiamata a Dispose (false) dovrebbe solo ripulire le risorse native. Una chiamata a Dispose (true) dovrebbe ripulire sia le risorse gestite che quelle native. stman User.cs 10

Per riferimento: CA1063: Implementare IDisposable correttamente

Ho letto questa pagina, ma temo di non capire davvero cosa debba essere fatto qui.

Se qualcuno può spiegare in termini più lusinghieri quale sia il problema e / o come debba essere implementato IDisposable, questo sarà di grande aiuto!


1
È tutto il codice dentro Dispose?
Claudio Redi,

42
È necessario implementare il metodo Dispose () per chiamare il metodo Dispose () su uno qualsiasi dei membri della classe. Nessuno di questi membri ne ha uno. Pertanto non è necessario implementare IDisposable. Il ripristino dei valori delle proprietà è inutile.
Hans Passant,

13
Hai solo bisogno di implementare IDispoablese si dispone di risorse non gestite da smaltire (questo include risorse non gestite che vengono avvolti ( SqlConnection, FileStream, etc.). Non si e non si deve applicare IDisposablese solo riusciti risorse come qui. Questo è, IMO, un grosso problema con l'analisi del codice. È molto bravo a controllare le stupide piccole regole, ma non è bravo a controllare gli errori concettuali.
Jason

51
Per me è abbastanza sconvolgente che alcune persone preferiscano sottovalutare e vedere chiusa questa domanda piuttosto che tentare di aiutare una persona che ha chiaramente frainteso un concetto. Che peccato.
Ortund,

2
Quindi non sottovalutare, non aumentare il voto, lasciare il post a zero e chiudere la domanda con un puntatore utile.
tjmoore,

Risposte:


113

Questa sarebbe l'implementazione corretta, anche se non vedo nulla che devi smaltire nel codice che hai pubblicato. Devi solo implementare IDisposablequando:

  1. Hai risorse non gestite
  2. Ti stai aggrappando a riferimenti di cose che sono essi stessi disponibili.

Nulla nel codice che hai pubblicato deve essere eliminato.

public class User : IDisposable
{
    public int id { get; protected set; }
    public string name { get; protected set; }
    public string pass { get; protected set; }

    public User(int userID)
    {
        id = userID;
    }
    public User(string Username, string Password)
    {
        name = Username;
        pass = Password;
    }

    // Other functions go here...

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (disposing) 
        {
            // free managed resources
        }
        // free native resources if there are any.
    }
}

2
Mi è stato detto quando ho iniziato a scrivere in C # che è meglio usarlo using(){ }ogni volta che è possibile, ma per farlo, è necessario implementare IDisposable, quindi in generale, preferisco accedere a una classe tramite usings, esp. se ho solo bisogno della lezione in una o due funzioni
Ortund

62
@Ortund Hai frainteso. È meglio usare un usingblocco quando la classe implementa IDisposable . Se non hai bisogno di una classe per essere usa e getta, non implementarla. Non serve a niente.
Daniel Mann,

5
@DanielMann Tuttavia, la semantica di un usingblocco tende ad essere attraente oltre la IDisposablesola interfaccia. Immagino che ci siano stati più di alcuni abusi IDisposablesolo per scopi di scoping.
Thomas,

1
Proprio come una nota a margine se si desidera liberare risorse non gestite, è necessario includere Finalizer che chiama Dispose (false), che consentirà a GC di chiamare Finalizer quando si esegue la garbage collection (nel caso in cui Dispose non sia stato ancora chiamato) e correttamente libero non gestito risorse.
mariozski,

4
Senza un finalizzatore nella tua implementazione l'implementazione non ha GC.SuppressFinalize(this);senso. Come ha sottolineato @mariozski, un finalizzatore contribuirebbe a garantire che Disposevenga chiamato affatto se la classe non viene utilizzata all'interno di un usingblocco.
Haymo Kutschbach,

57

Prima di tutto, non c'è bisogno di "clean up" strings ed ints - esse verranno prese cura di automaticamente dal garbage collector. L'unica cosa che deve essere ripulita Disposesono le risorse non gestite o le risorse gestite che implementano IDisposable.

Tuttavia, supponendo che questo sia solo un esercizio di apprendimento, il modo consigliato per implementare IDisposableè aggiungere un "fermo di sicurezza" per garantire che qualsiasi risorsa non venga smaltita due volte:

public void Dispose()
{
    Dispose(true);

    // Use SupressFinalize in case a subclass 
    // of this type implements a finalizer.
    GC.SuppressFinalize(this);   
}
protected virtual void Dispose(bool disposing)
{
    if (!_disposed)
    {
        if (disposing) 
        {
            // Clear all property values that maybe have been set
            // when the class was instantiated
            id = 0;
            name = String.Empty;
            pass = String.Empty;
        }

        // Indicate that the instance has been disposed.
        _disposed = true;   
    }
}

3
+1, con un flag per assicurarsi che il codice di cleanup venga eseguito solo una volta è molto, molto meglio che impostare le proprietà su null o altro (specialmente perché interferisce con la readonlysemantica)
Thomas

+1 per l'utilizzo del codice utente (anche se verrà ripulito automaticamente) per chiarire cosa succede lì. Inoltre, per non essere un marinaio salato e averlo martellato per aver fatto un piccolo errore mentre imparava come molti altri qui.
Murphybro2,

42

L'esempio seguente mostra le migliori pratiche generali per implementare l' IDisposableinterfaccia. Riferimento

Tieni presente che hai bisogno di un distruttore (finalizzatore) solo se hai risorse non gestite nella tua classe. E se aggiungi un distruttore, dovresti sopprimere la finalizzazione in Dispose , altrimenti i tuoi oggetti risiederanno in memoria per due cicli di immondizia (Nota: leggi come funziona la finalizzazione ). Di seguito esempio elaborato tutto sopra.

public class DisposeExample
{
    // A base class that implements IDisposable. 
    // By implementing IDisposable, you are announcing that 
    // instances of this type allocate scarce resources. 
    public class MyResource: IDisposable
    {
        // Pointer to an external unmanaged resource. 
        private IntPtr handle;
        // Other managed resource this class uses. 
        private Component component = new Component();
        // Track whether Dispose has been called. 
        private bool disposed = false;

        // The class constructor. 
        public MyResource(IntPtr handle)
        {
            this.handle = handle;
        }

        // Implement IDisposable. 
        // Do not make this method virtual. 
        // A derived class should not be able to override this method. 
        public void Dispose()
        {
            Dispose(true);
            // This object will be cleaned up by the Dispose method. 
            // Therefore, you should call GC.SupressFinalize to 
            // take this object off the finalization queue 
            // and prevent finalization code for this object 
            // from executing a second time.
            GC.SuppressFinalize(this);
        }

        // Dispose(bool disposing) executes in two distinct scenarios. 
        // If disposing equals true, the method has been called directly 
        // or indirectly by a user's code. Managed and unmanaged resources 
        // can be disposed. 
        // If disposing equals false, the method has been called by the 
        // runtime from inside the finalizer and you should not reference 
        // other objects. Only unmanaged resources can be disposed. 
        protected virtual void Dispose(bool disposing)
        {
            // Check to see if Dispose has already been called. 
            if(!this.disposed)
            {
                // If disposing equals true, dispose all managed 
                // and unmanaged resources. 
                if(disposing)
                {
                    // Dispose managed resources.
                    component.Dispose();
                }

                // Call the appropriate methods to clean up 
                // unmanaged resources here. 
                // If disposing is false, 
                // only the following code is executed.
                CloseHandle(handle);
                handle = IntPtr.Zero;

                // Note disposing has been done.
                disposed = true;

            }
        }

        // Use interop to call the method necessary 
        // to clean up the unmanaged resource.
        [System.Runtime.InteropServices.DllImport("Kernel32")]
        private extern static Boolean CloseHandle(IntPtr handle);

        // Use C# destructor syntax for finalization code. 
        // This destructor will run only if the Dispose method 
        // does not get called. 
        // It gives your base class the opportunity to finalize. 
        // Do not provide destructors in types derived from this class.
        ~MyResource()
        {
            // Do not re-create Dispose clean-up code here. 
            // Calling Dispose(false) is optimal in terms of 
            // readability and maintainability.
            Dispose(false);
        }
    }
    public static void Main()
    {
        // Insert code here to create 
        // and use the MyResource object.
    }
}

14

IDisposableesiste per fornire un mezzo per ripulire le risorse non gestite che non verranno ripulite automaticamente dal Garbage Collector.

Tutte le risorse che stai "ripulendo" sono risorse gestite e come tale il tuo Disposemetodo non sta realizzando nulla. La tua classe non dovrebbe implementarsi IDisposableaffatto. Il Garbage Collector si prenderà cura di tutti quei campi da solo.


1
Concordo con questo: esiste l'idea di smaltire tutto quando non è effettivamente necessario. Smaltire dovrebbe essere utilizzato solo se si dispone di risorse non gestite per la pulizia !!
Chandramouleswaran Ravichandra,

4
Non strettamente vero, il metodo Dispose consente anche di smaltire "risorse gestite che implementano IDisposable"
Matt Wilko,

@MattWilko Questo lo rende un modo indiretto di disporre di risorse non gestite, perché consente a un'altra risorsa di disporre di una risorsa non gestita. Qui non c'è nemmeno un riferimento indiretto a una risorsa non gestita attraverso un'altra risorsa gestita.
Servito il

@MattWilko Dispose chiamerà automaticamente qualsiasi risorsa gestita che ha implementato IDesposable
panky sharma,

@pankysharma No, non lo farà. Deve essere chiamato manualmente . Questo è il punto. Non puoi presumere che verrà chiamato automaticamente, sai solo che le persone dovrebbero chiamarlo manualmente, ma le persone fanno errori e dimenticano.
Servito dal

14

È necessario utilizzare il modello monouso in questo modo:

private bool _disposed = false;

protected virtual void Dispose(bool disposing)
{
    if (!_disposed)
    {
        if (disposing)
        {
            // Dispose any managed objects
            // ...
        }

        // Now disposed of any unmanaged objects
        // ...

        _disposed = true;
    }
}

public void Dispose()
{
    Dispose(true);
    GC.SuppressFinalize(this);  
}

// Destructor
~YourClassName()
{
    Dispose(false);
}

1
non sarebbe più saggio ricevere una chiamata a GC.SuppressFinalize (anche questo) nel distruttore? Altrimenti l'oggetto stesso verrebbe reclamato nel prossimo CG
Sudhanshu Mishra

2
@dotnetguy: il distruttore di oggetti viene chiamato quando gc viene eseguito. Quindi non è possibile chiamare due volte. Vedi qui: msdn.microsoft.com/en-us/library/ms244737.aspx
schoetbi

1
Quindi ora stiamo chiamando un pezzo di codice boilerplate un "modello"?
Chel

4
@rdhs No, non lo siamo. MSDN afferma che è un modello "Dispose Pattern" qui - msdn.microsoft.com/en-us/library/b1yfkh5e(v=vs.110).aspx quindi prima di votare verso il basso forse Google un po '?
Belogix,

2
Né Microsoft né i tuoi post dichiarano chiaramente perché il modello dovrebbe apparire così. In generale, non è nemmeno un boilerplate, è solo superfluo - sostituito da SafeHandle(e sottotipi). Nel caso delle risorse gestite, l'implementazione di uno smaltimento adeguato diventa molto più semplice; puoi tagliare il codice in una semplice implementazione del void Dispose()metodo.
BatteryBackupUnit

10

Non è necessario eseguire la Userlezione IDisposablepoiché la classe non acquisisce risorse non gestite (file, connessione al database, ecc.). Di solito, contrassegniamo le classi come IDisposablese avessero almeno un IDisposablecampo o / e proprietà. Durante l'implementazione IDisposable, meglio inserirlo secondo lo schema tipico di Microsoft:

public class User: IDisposable {
  ...
  protected virtual void Dispose(Boolean disposing) {
    if (disposing) {
      // There's no need to set zero empty values to fields 
      // id = 0;
      // name = String.Empty;
      // pass = String.Empty;

      //TODO: free your true resources here (usually IDisposable fields)
    }
  }

  public void Dispose() {
    Dispose(true);

    GC.SuppressFinalize(this);
  } 
}

Di solito è così. D'altra parte, il costrutto using apre la possibilità di scrivere qualcosa di simile ai puntatori intelligenti C ++, vale a dire un oggetto che ripristinerà lo stato precedente indipendentemente da come esce un blocco using. L'unico modo che ho trovato di fare questo è rendere IDisposable un tale oggetto implementare. Sembra che io possa ignorare l'avvertimento del compilatore in un caso d'uso così marginale.
Papa Puffo,

3

Idisposable è implementato ogni volta che si desidera una garbage collection deterministica (confermata).

class Users : IDisposable
    {
        ~Users()
        {
            Dispose(false);
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
            // This method will remove current object from garbage collector's queue 
            // and stop calling finilize method twice 
        }    

        public void Dispose(bool disposer)
        {
            if (disposer)
            {
                // dispose the managed objects
            }
            // dispose the unmanaged objects
        }
    }

Quando si crea e si utilizza la classe Users, utilizzare il blocco "using" per evitare di chiamare in modo esplicito il metodo dispose:

using (Users _user = new Users())
            {
                // do user related work
            }

la fine del blocco usando l'oggetto Users creato verrà eliminata dal metodo implicito invoke of dispose.


2

Vedo molti esempi del pattern Microsoft Dispose che è in realtà un anti-pattern. Come molti hanno sottolineato il codice nella domanda non richiede affatto IDisposable. Ma se hai intenzione di implementarlo, non utilizzare il modello Microsoft. Una risposta migliore sarebbe seguire i suggerimenti in questo articolo:

https://www.codeproject.com/Articles/29534/IDisposable-What-Your-Mother-Never-Told-You-About

L'unica altra cosa che potrebbe essere utile è sopprimere l'avviso di analisi del codice ... https://docs.microsoft.com/en-us/visualstudio/code-quality/in-source-suppression-overview?view=vs- 2017

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.