Utilisation de la méthode Finalize / Dispose en C #

381

C # 2008

J'y travaille depuis un certain temps maintenant, et je suis toujours confus quant à l'utilisation des méthodes de finalisation et d'élimination dans le code. Mes questions sont ci-dessous:

  1. Je sais que nous n'avons besoin que d'un finaliseur pour disposer de ressources non gérées. Cependant, s'il existe des ressources gérées qui appellent des ressources non gérées, aurait-il encore besoin d'implémenter un finaliseur?

  2. Cependant, si je développe une classe qui n'utilise aucune ressource non gérée - directement ou indirectement, dois-je implémenter le IDisposablepour autoriser les clients de cette classe à utiliser «l'instruction using»?

    Serait-il possible d'implémenter IDisposable juste pour permettre aux clients de votre classe d'utiliser l'instruction using?

    using(myClass objClass = new myClass())
    {
        // Do stuff here
    }
  3. J'ai développé ce code simple ci-dessous pour démontrer l'utilisation de Finaliser / éliminer:

    public class NoGateway : IDisposable
    {
        private WebClient wc = null;
    
        public NoGateway()
        {
            wc = new WebClient();
            wc.DownloadStringCompleted += wc_DownloadStringCompleted;
        }
    
    
        // Start the Async call to find if NoGateway is true or false
        public void NoGatewayStatus()
        {
            // Start the Async's download
                // Do other work here
            wc.DownloadStringAsync(new Uri(www.xxxx.xxx));
        }
    
        private void wc_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
        {
            // Do work here
        }
    
        // Dispose of the NoGateway object
        public void Dispose()
        {
            wc.DownloadStringCompleted -= wc_DownloadStringCompleted;
            wc.Dispose();
            GC.SuppressFinalize(this);
        }
    }

Question sur le code source:

  1. Ici, je n'ai pas ajouté le finaliseur, et normalement le finaliseur sera appelé par le GC, et le finaliseur appellera Dispose. Comme je n'ai pas le finaliseur, quand dois-je appeler la méthode Dispose? Est-ce le client de la classe qui doit l'appeler?

    Donc ma classe dans l'exemple s'appelle NoGateway et le client pourrait utiliser et éliminer la classe comme ceci:

    using(NoGateway objNoGateway = new NoGateway())
    {
        // Do stuff here   
    }

    La méthode Dispose serait-elle automatiquement appelée lorsque l'exécution atteindrait la fin du bloc using, ou le client doit-il appeler manuellement la méthode Dispose? c'est à dire

    NoGateway objNoGateway = new NoGateway();
    // Do stuff with object
    objNoGateway.Dispose(); // finished with it
  2. J'utilise la WebClientclasse dans ma NoGatewayclasse. Parce WebClientqu'implémente l' IDisposableinterface, cela signifie-t-il que WebClientindirectement utilise des ressources non gérées? Existe-t-il une règle stricte et rapide pour suivre cela? Comment savoir qu'une classe utilise des ressources non managées?

ant2009
la source
1
ce modèle de conception complexe est-il réellement nécessaire pour résoudre ce problème de relocalisation des ressources?
zinking

Réponses:

422

Le modèle IDisposable recommandé est ici . Lors de la programmation d'une classe qui utilise IDisposable, vous devez généralement utiliser deux modèles:

Lorsque vous implémentez une classe scellée qui n'utilise pas de ressources non managées, vous implémentez simplement une méthode Dispose comme avec les implémentations d'interface normales:

public sealed class A : IDisposable
{
    public void Dispose()
    {
        // get rid of managed resources, call Dispose on member variables...
    }
}

Lorsque vous implémentez une classe non scellée, procédez comme suit:

public class B : IDisposable
{    
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (disposing)
        {
            // get rid of managed resources
        }   
        // get rid of unmanaged resources
    }

    // only if you use unmanaged resources directly in B
    //~B()
    //{
    //    Dispose(false);
    //}
}

Notez que je n'ai pas déclaré de finaliseur dans B; vous ne devez implémenter un finaliseur que si vous disposez de ressources réelles non gérées à éliminer. Le CLR traite les objets finalisables différemment des objets non finalisables, même s'il SuppressFinalizeest appelé.

Donc, vous ne devez pas déclarer un finaliseur sauf si vous le devez, mais vous donnez aux héritiers de votre classe un hook pour appeler votre Disposeet implémenter un finaliseur eux-mêmes s'ils utilisent directement des ressources non managées:

public class C : B
{
    private IntPtr m_Handle;

    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            // get rid of managed resources
        }
        ReleaseHandle(m_Handle);

        base.Dispose(disposing);
    }

    ~C() {
        Dispose(false);
    }
}

Si vous n'utilisez pas directement des ressources non gérées ( SafeHandleet que les amis ne comptent pas, car ils déclarent leurs propres finaliseurs), n'implémentez pas de finaliseur, car le GC traite les classes finalisables différemment, même si vous supprimez plus tard le finaliseur. Notez également que, même s'il Bn'a pas de finaliseur, il appelle toujours SuppressFinalizepour traiter correctement les sous-classes qui implémentent un finaliseur.

Lorsqu'une classe implémente l'interface IDisposable, cela signifie qu'il existe quelque part des ressources non gérées dont vous devez vous débarrasser lorsque vous avez fini d'utiliser la classe. Les ressources réelles sont encapsulées dans les classes; vous n'avez pas besoin de les supprimer explicitement. Un simple appel Dispose()ou encapsulation de la classe dans a using(...) {}vous permettra de vous débarrasser de toutes les ressources non gérées si nécessaire.

thecoop
la source
26
Je suis d'accord avec thecoop. Notez que vous n'avez pas besoin d'un finaliseur si vous ne traitez qu'avec des ressources gérées (en fait, vous ne devriez PAS essayer d'accéder aux objets gérés depuis votre finaliseur (autre que "ceci"), car il n'y a pas d'ordre garanti dans lequel le GC nettoiera les objets. De plus, si vous utilisez .Net 2.0 ou supérieur, vous pouvez (et devez) utiliser SafeHandles pour encapsuler les poignées non gérées. Les Safehandles réduisent considérablement votre besoin d'écrire des finaliseurs pour vos classes gérées. Blogs.msdn. com / bclteam / archive / 2005/03/16 / 396900.aspx
JMarsch
5
Je pense qu'il est préférable de passer un appel à MessageBox.Show ("Erreur" + GetType (). Nom + "non supprimé") dans le finaliseur, car l'objet jetable doit TOUJOURS être supprimé, et si vous ne le faites pas, c'est mieux être alerté dès que possible.
erikkallen
95
@erikkallen est-ce une blague? :)
Alex Norcliffe
2
car un effort informatique supplémentaire est nécessaire dans le CLR pour suivre les cours avec les finaliseurs actifs. - L'implémentation d'un finaliseur provoque cela. L'appel de GC.SuppressFinalize signifie que le Finalizer ne doit pas être appelé par le runtime. Il passe toujours Gen2 malgré tout. N'ajoutez pas de finaliseur si vous ne traitez pas avec des ressources gérées. Les modificateurs de classe scellés ou non scellés ne sont pas pertinents à ce stade.
Ritch Melton
3
@Ritch: citation? Ce n'est pas nécessairement une mauvaise chose; si vous mettez en œuvre IDisposable, il y a de fortes chances que cela traîne pendant un certain temps de toute façon. Vous économisez le CLR l'effort d'avoir à le copier de Gen0 -> Gen1 -> Gen2
thecoop
123

Le modèle officiel à mettre en œuvre IDisposableest difficile à comprendre. Je crois que celui-ci est meilleur :

public class BetterDisposableClass : IDisposable {

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

  protected virtual void CleanUpManagedResources() { 
    // ...
  }
  protected virtual void CleanUpNativeResources() {
    // ...
  }

  ~BetterDisposableClass() {
    CleanUpNativeResources();
  }

}

Une solution encore meilleure consiste à avoir une règle selon laquelle vous devez toujours créer une classe wrapper pour toute ressource non managée que vous devez gérer:

public class NativeDisposable : IDisposable {

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

  protected virtual void CleanUpNativeResource() {
    // ...
  }

  ~NativeDisposable() {
    CleanUpNativeResource();
  }

}

Avec SafeHandleet ses dérivés, ces classes devraient être très rares .

Le résultat pour les classes jetables qui ne traitent pas directement avec les ressources non managées, même en présence d'héritage, est puissant: elles n'ont plus besoin de se préoccuper des ressources non managées . Ils seront simples à mettre en œuvre et à comprendre:

public class ManagedDisposable : IDisposable {

  public virtual void Dispose() {
    // dispose of managed resources
  }

}
Jordão
la source
@Kyle: Merci! Je l'aime aussi :-) Il y a un suivi ici .
Jordão
4
Bien qu'une chose que je veux noter est qu'elle n'empêche pas d'être appelée une deuxième fois.
HuseyinUslu
5
@HuseyinUslu: ce n'est que l' essence du motif. Vous pouvez certainement ajouter un disposedindicateur et vérifier en conséquence.
Jordão
2
@didibus: il suffit d'ajouter un disposedindicateur, de le vérifier avant de le supprimer et de le définir après sa suppression. Regardez ici pour l'idée. Vous devez également vérifier l'indicateur avant toutes les méthodes de la classe. Logique? C'est compliqué?
Jordão
1
+1 pour "Une solution encore meilleure est d'avoir une règle selon laquelle vous devez toujours créer une classe wrapper pour toute ressource non managée que vous devez gérer" . Je suis tombé sur cela dans un addon pour VLC et je l'utilise depuis. Sauve tant de maux de tête ...
Franz B.
37

Notez que toute mise en œuvre IDisposable doit suivre le modèle ci-dessous (à mon humble avis). J'ai développé ce modèle sur la base des informations provenant de plusieurs excellents «dieux» .NET des directives de conception du .NET Framework (notez que MSDN ne suit pas cela pour une raison quelconque!). Les lignes directrices de conception du .NET Framework ont ​​été écrites par Krzysztof Cwalina (architecte CLR à l'époque) et Brad Abrams (je crois que le responsable du programme CLR à l'époque) et Bill Wagner ([Effective C #] et [More Effective C #] (prenez simplement un recherchez-les sur Amazon.com:

Notez que vous ne devez JAMAIS implémenter un Finalizer à moins que votre classe ne contienne directement (et n'hérite pas) des ressources UNmanaged. Une fois que vous implémentez un Finalizer dans une classe, même s'il n'est jamais appelé, il est garanti de vivre pour une collection supplémentaire. Il est automatiquement placé dans la file d'attente de finalisation (qui s'exécute sur un seul thread). De plus, une note très importante ... tout le code exécuté dans un Finalizer (si vous devez en implémenter un) DOIT être thread-safe ET exception-safe! MAUVAISES choses se produiront autrement ... (c'est-à-dire un comportement indéterminé et dans le cas d'une exception, un plantage fatal irrécupérable de l'application).

Le modèle que j'ai mis en place (et écrit un extrait de code pour) suit:

#region IDisposable implementation

//TODO remember to make this class inherit from IDisposable -> $className$ : IDisposable

// Default initialization for a bool is 'false'
private bool IsDisposed { get; set; }

/// <summary>
/// Implementation of Dispose according to .NET Framework Design Guidelines.
/// </summary>
/// <remarks>Do not make this method virtual.
/// A derived class should not be able to override this method.
/// </remarks>
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.

    // Always use SuppressFinalize() in case a subclass
    // of this type implements a finalizer.
    GC.SuppressFinalize( this );
}

/// <summary>
/// Overloaded Implementation of Dispose.
/// </summary>
/// <param name="isDisposing"></param>
/// <remarks>
/// <para><list type="bulleted">Dispose(bool isDisposing) executes in two distinct scenarios.
/// <item>If <paramref name="isDisposing"/> equals true, the method has been called directly
/// or indirectly by a user's code. Managed and unmanaged resources
/// can be disposed.</item>
/// <item>If <paramref name="isDisposing"/> 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.</item></list></para>
/// </remarks>
protected virtual void Dispose( bool isDisposing )
{
    // TODO If you need thread safety, use a lock around these 
    // operations, as well as in your methods that use the resource.
    try
    {
        if( !this.IsDisposed )
        {
            if( isDisposing )
            {
                // TODO Release all managed resources here

                $end$
            }

            // TODO Release all unmanaged resources here



            // TODO explicitly set root references to null to expressly tell the GarbageCollector
            // that the resources have been disposed of and its ok to release the memory allocated for them.


        }
    }
    finally
    {
        // explicitly call the base class Dispose implementation
        base.Dispose( isDisposing );

        this.IsDisposed = true;
    }
}

//TODO Uncomment this code if this class will contain members which are UNmanaged
// 
///// <summary>Finalizer for $className$</summary>
///// <remarks>This finalizer will run only if the Dispose method does not get called.
///// It gives your base class the opportunity to finalize.
///// DO NOT provide finalizers in types derived from this class.
///// All code executed within a Finalizer MUST be thread-safe!</remarks>
//  ~$className$()
//  {
//     Dispose( false );
//  }
#endregion IDisposable implementation

Voici le code pour implémenter IDisposable dans une classe dérivée. Notez que vous n'avez pas besoin de répertorier explicitement l'héritage d'IDisposable dans la définition de la classe dérivée.

public DerivedClass : BaseClass, IDisposable (remove the IDisposable because it is inherited from BaseClass)


protected override void Dispose( bool isDisposing )
{
    try
    {
        if ( !this.IsDisposed )
        {
            if ( isDisposing )
            {
                // Release all managed resources here

            }
        }
    }
    finally
    {
        // explicitly call the base class Dispose implementation
        base.Dispose( isDisposing );
    }
}

J'ai publié cette implémentation sur mon blog à: Comment implémenter correctement le modèle de suppression

Dave Black
la source
Quelqu'un peut-il également ajouter un modèle pour une classe dérivée (dérivant de cette classe de base)
akjoshi
3
@akjoshi - J'ai mis à jour le modèle ci-dessus pour inclure le code d'une classe jetable dérivée. A noter également, NE JAMAIS implémenter un Finalizer dans une classe dérivée ...
Dave Black
3
Microsoft semble aimer définir l'indicateur «supprimé» à la fin de la méthode supprimée, mais cela me semble incorrect. Les appels redondants à «Dispose» sont censés ne rien faire; alors que l'on ne s'attend pas normalement à ce que Dispose soit appelé de manière récursive, de telles choses peuvent se produire si l'on essaie de supprimer un objet qui a été laissé dans un état invalide par une exception qui s'est produite pendant la construction ou une autre opération. Je pense que l'utilisation d' Interlocked.Exchangeun IsDisposedindicateur sur un entier dans la fonction wrapper non virtuel serait plus sûre.
supercat
@DaveBlack: que se passe-t-il si votre classe de base n'utilise pas de ressources non gérées, mais que votre classe dérivée le fait? Devez-vous alors implémenter le Finalizer dans la classe dérivée? Et si oui, comment savez-vous que la classe de base ne l'a pas déjà implémentée si vous n'avez pas accès à la source?
Didier A.
@DaveBlack "J'ai développé ce modèle sur la base des informations provenant de plusieurs excellents" dieux ".NET" "Si l'un des dieux était Jon Skeet, je suivrais vos conseils.
Elisabeth
23

Je suis d' accord avec pm100 (et j'aurais dû le dire explicitement dans mon post précédent).

Vous ne devez jamais implémenter IDisposable dans une classe à moins d'en avoir besoin. Pour être très précis, il y a environ 5 fois où vous auriez besoin / devriez implémenter IDisposable:

  1. Votre classe contient explicitement (c'est-à-dire pas via l'héritage) toutes les ressources gérées qui implémentent IDisposable et doivent être nettoyées une fois que votre classe n'est plus utilisée. Par exemple, si votre classe contient une instance d'un Stream, DbCommand, DataTable, etc.

  2. Votre classe contient explicitement toutes les ressources gérées qui implémentent une méthode Close () - par exemple IDataReader, IDbConnection, etc. Notez que certaines de ces classes implémentent IDisposable en ayant Dispose () ainsi qu'une méthode Close ().

  3. Votre classe contient explicitement une ressource non gérée - par exemple un objet COM, des pointeurs (oui, vous pouvez utiliser des pointeurs en C # géré mais ils doivent être déclarés dans des blocs "non sécurisés", etc. Dans le cas de ressources non gérées, vous devez également vous assurer de appelez System.Runtime.InteropServices.Marshal.ReleaseComObject () sur le RCW. Même si le RCW est, en théorie, un wrapper managé, il y a toujours un comptage de références en cours sous les couvertures.

  4. Si votre classe s'abonne à des événements en utilisant des références fortes. Vous devez vous désinscrire / vous détacher des événements. Assurez-vous toujours que ceux-ci ne sont pas nuls avant d'essayer de les désenregistrer / les détacher!.

  5. Votre classe contient toute combinaison des éléments ci-dessus ...

Une alternative recommandée à l'utilisation d'objets COM et à l'utilisation de Marshal.ReleaseComObject () consiste à utiliser la classe System.Runtime.InteropServices.SafeHandle.

La BCL (Base Class Library Team) a un bon blog à ce sujet ici http://blogs.msdn.com/bclteam/archive/2005/03/16/396900.aspx

Une note très importante à faire est que si vous travaillez avec WCF et que vous nettoyez des ressources, vous devriez PRESQUE TOUJOURS éviter le bloc "using". Il existe de nombreux articles de blog et certains sur MSDN expliquant pourquoi c'est une mauvaise idée. J'ai également posté à ce sujet ici - N'utilisez pas 'using ()' avec un proxy WCF

Dave Black
la source
3
Je crois qu'il y a un 5ème cas: si votre classe s'abonne à des événements en utilisant des références fortes, alors vous devez implémenter IDisposable et vous désinscrire des événements de la méthode Dispose.
Didier A.
Salut didibus. Oui tu as raison. J'ai oublié à propos de celui là. J'ai modifié ma réponse pour l'inclure comme cas. Merci.
Dave Black
La documentation MSDN pour le modèle d'élimination ajoute un autre cas: "CONSIDÉRER l'implémentation du modèle d'élimination de base sur les classes qui elles-mêmes ne contiennent pas de ressources non gérées ou d'objets jetables mais qui sont susceptibles d'avoir des sous-types qui le font. Un bon exemple de ceci est le System.IO .Stream class. Bien qu'il s'agisse d'une classe de base abstraite qui ne détient aucune ressource, la plupart de ses sous-classes le font et à cause de cela, elle implémente ce modèle. "
Gonen I
12

Utilisation de lambdas au lieu d'IDisposable.

Je n'ai jamais été ravi de l'idée d'utiliser / IDisposable. Le problème est qu'il nécessite que l'appelant:

  • savent qu'ils doivent utiliser IDisposable
  • n'oubliez pas d'utiliser «en utilisant».

Ma nouvelle méthode préférée consiste à utiliser une méthode d'usine et un lambda à la place

Imaginez que je veux faire quelque chose avec un SqlConnection (quelque chose qui devrait être enveloppé dans une utilisation). Classiquement, vous feriez

using (Var conn = Factory.MakeConnection())
{
     conn.Query(....);
}

Nouvelle façon

Factory.DoWithConnection((conn)=>
{
    conn.Query(...);
}

Dans le premier cas, l'appelant ne pouvait tout simplement pas utiliser la syntaxe using. Dans le second cas, l'utilisateur n'a pas le choix. Aucune méthode ne crée un objet SqlConnection, l'appelant doit appeler DoWithConnection.

DoWithConnection ressemble à ceci

void DoWithConnection(Action<SqlConnection> action)
{
   using (var conn = MakeConnection())
   {
       action(conn);
   }
}

MakeConnection est maintenant privé

pm100
la source
2
Emballer des choses dans des lambdas peut être une bonne approche, mais cela a des limites. Ce n'est pas trop mal pour les situations où, en fait, tous les consommateurs d'une classe utiliseraient un bloc "using", mais cela interdirait les situations où une méthode stockerait un IDisposable dans un champ de classe (soit directement, soit dans quelque chose comme un itérateur ).
supercat
@supercat, vous pouvez affirmer que l'interdiction du stockage de ressources monopolistiques est une bonne chose. Le modèle d'emprunt que je propose ici vous oblige à être maigre avec votre utilisation de la ressource
pm100
Cela peut être une bonne chose, mais cela peut aussi rendre très difficiles certaines opérations très raisonnables. Par exemple, supposons qu'un type de lecteur de base de données, au lieu d'implémenter IEnumerable <T>, expose une méthode DoForAll(Action<T>) where T:IComparable<T>, en appelant le délégué indiqué sur chaque enregistrement. Étant donné deux de ces objets, qui renverront tous deux des données dans un ordre trié, comment sortirait-on tous les éléments qui existent dans une collection mais pas l'autre? Si les types sont implémentés IEnumerable<T>, on pourrait effectuer une opération de fusion, mais cela ne fonctionnera pas avec DoForAll.
supercat
La seule façon dont je peux imaginer de fusionner deux DoForAllcollections sans avoir à copier d'abord une, dans son intégralité, dans une autre structure, serait d'utiliser deux threads, ce qui serait plutôt plus lourd de ressources que d'utiliser simplement quelques IEnumerable et en faisant attention pour les libérer.
supercat
-1: bonne réponse à une question qui n'a pas été posée. Ce serait une excellente réponse à "comment puis-je faciliter la consommation d'objets IDisposable"
John Saunders
10

personne n'a répondu à la question de savoir si vous devez implémenter IDisposable même si vous n'en avez pas besoin.

Réponse courte: Non

Longue réponse:

Cela permettrait à un consommateur de votre classe d'utiliser «using». La question que je poserais est - pourquoi le feraient-ils? La plupart des développeurs n'utiliseront pas «à l'aide» à moins qu'ils ne sachent qu'ils doivent - et comment savent-ils. Soit

  • ses obviuos les d'expérience (une classe de socket par exemple)
  • son documenté
  • ils sont prudents et peuvent voir que la classe implémente IDisposable

Donc, en implémentant IDisposable, vous dites aux développeurs (au moins certains) que cette classe termine quelque chose qui doit être publié. Ils utiliseront «using» - mais il y a d'autres cas où l'utilisation n'est pas possible (la portée de l'objet n'est pas locale); et ils devront commencer à s'inquiéter de la durée de vie des objets dans ces autres cas - je m'inquiéterais à coup sûr. Mais ce n'est pas nécessaire

Vous implémentez Idisposable pour leur permettre d'utiliser en utilisant, mais ils n'utiliseront pas en utilisant à moins que vous leur dites.

Alors ne le fais pas

pm100
la source
1
Je ne comprends pas pourquoi un développeur n'utiliserait pas l'utilisation / disposer sur un objet implémentant IDisposable (sauf si le programme est sur le point de quitter de toute façon).
adrianm
1
le fait est qu'un développeur devrait écrire tous les appels pour se débarrasser de toutes les voies de code qui entraînent son non-référencement. Par exemple, si je mets une instance dans un dictionnaire, lorsque je supprime des entrées du dictionnaire, je dois appeler dispose. C'est beaucoup de tracas qui ne sont pas nécessaires dans ce cas - l'objet n'a pas besoin d'être éliminé
pm100
3
@ pm100 Re: Implémenter inutilement IDisposable - Il y a un article détaillé sur codeproject.com/KB/dotnet/idisposable.aspx qui discute de quelques cas rares où vous voudrez peut-être y penser (très rare, j'en suis sûr). En bref: si vous pouvez prévoir le besoin d'IDisposable à l'avenir, ou dans un objet dérivé, alors vous pourriez penser à implémenter IDisposable comme un "no-op" dans votre classe de base pour éviter les problèmes de "découpage" où certains objets dérivés nécessitent l'élimination et d'autres pas.
Kevin P. Rice
4
  1. Si vous utilisez d'autres objets gérés qui utilisent des ressources non gérées, il n'est pas de votre responsabilité de vous assurer qu'ils sont finalisés. Votre responsabilité est d'appeler Dispose sur ces objets lorsque Dispose est appelé sur votre objet, et cela s'arrête là.

  2. Si votre classe n'utilise pas de ressources rares, je ne vois pas pourquoi vous voudriez que votre classe implémente IDisposable. Vous ne devez le faire que si vous êtes:

    • Sachez que vous aurez bientôt des ressources rares dans vos objets, mais pas maintenant (et je veux dire que comme dans "nous continuons à développer, ce sera ici avant que nous ayons terminé", pas comme dans "je pense que nous en aurons besoin ")
    • Utiliser des ressources rares
  3. Oui, le code qui utilise votre code doit appeler la méthode Dispose de votre objet. Et oui, le code qui utilise votre objet peut utiliser usingcomme vous l'avez montré.

  4. (2 encore?) Il est probable que le WebClient utilise des ressources non managées ou d'autres ressources managées qui implémentent IDisposable. La raison exacte, cependant, n'est pas importante. Ce qui est important, c'est qu'il implémente IDisposable, et il vous incombe donc d'agir sur cette connaissance en éliminant l'objet lorsque vous en avez terminé, même s'il s'avère que WebClient n'utilise aucune autre ressource.

Lasse V. Karlsen
la source
4

Éliminer le motif:

public abstract class DisposableObject : IDisposable
{
    public bool Disposed { get; private set;}      

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

    ~DisposableObject()
    {
        Dispose(false);
    }

    private void Dispose(bool disposing)
    {
        if (!Disposed)
        {
            if (disposing)
            {
                DisposeManagedResources();
            }

            DisposeUnmanagedResources();
            Disposed = true;
        }
    }

    protected virtual void DisposeManagedResources() { }
    protected virtual void DisposeUnmanagedResources() { }
}

Exemple d'héritage:

public class A : DisposableObject
{
    public Component components_a { get; set; }
    private IntPtr handle_a;

    protected override void DisposeManagedResources()
    {
        try
        {
          Console.WriteLine("A_DisposeManagedResources");
          components_a.Dispose();
          components_a = null;
        }
        finally
        { 
          base.DisposeManagedResources();
        }
    }

    protected override void DisposeUnmanagedResources()
    {
        try
        {
          Console.WriteLine("A_DisposeUnmanagedResources");
          CloseHandle(handle_a);
          handle_a = IntPtr.Zero;
        }
        finally
        { 
          base.DisposeUnmanagedResources();
        }
    }
}

public class B : A
{
    public Component components_b { get; set; }
    private IntPtr handle_b;

    protected override void DisposeManagedResources()
    {
        try
        {
          Console.WriteLine("B_DisposeManagedResources");
          components_b.Dispose();
          components_b = null;
        }
        finally
        { 
          base.DisposeManagedResources();
        }
    }

    protected override void DisposeUnmanagedResources()
    {
        try
        {
          Console.WriteLine("B_DisposeUnmanagedResources");
          CloseHandle(handle_b);
          handle_b = IntPtr.Zero;
        }
        finally
        { 
          base.DisposeUnmanagedResources();
        }
    }
}
Andrei Krasutski
la source
4

Certains aspects d' une autre réponse sont légèrement incorrects pour 2 raisons:

Première,

using(NoGateway objNoGateway = new NoGateway())

est en fait équivalent à:

try
{
    NoGateway = new NoGateway();
}
finally
{
    if(NoGateway != null)
    {
        NoGateway.Dispose();
    }
}

Cela peut sembler ridicule, car le «nouvel» opérateur ne doit jamais retourner «null», sauf si vous avez une exception OutOfMemory. Mais considérez les cas suivants: 1. Vous appelez une FactoryClass qui renvoie une ressource IDisposable ou 2. Si vous avez un type qui peut ou non hériter d'IDisposable en fonction de son implémentation - rappelez-vous que j'ai vu le modèle IDisposable implémenté incorrectement beaucoup parfois sur de nombreux clients où les développeurs ajoutent simplement une méthode Dispose () sans hériter d'IDisposable (mauvais, mauvais, mauvais). Vous pouvez également avoir le cas d'une ressource IDisposable renvoyée par une propriété ou une méthode (encore une fois mauvaise, mauvaise, mauvaise - ne donnez pas vos ressources IDisposable)

using(IDisposable objNoGateway = new NoGateway() as IDisposable)
{
    if (NoGateway != null)
    {
        ...

Si l'opérateur «as» renvoie null (ou une propriété ou une méthode renvoyant la ressource) et que votre code dans le bloc «using» protège contre «null», votre code ne explosera pas lorsque vous tenterez d'appeler Dispose sur un objet null en raison de la vérification nulle «intégrée».

La deuxième raison pour laquelle votre réponse n'est pas exacte est due au stmt suivant:

Un finaliseur est appelé sur le GC détruisant votre objet

Premièrement, la finalisation (ainsi que le GC lui-même) n'est pas déterministe. Le CLR détermine quand il appellera un finaliseur. c'est-à-dire que le développeur / code n'a aucune idée. Si le modèle IDisposable est correctement implémenté (comme je l'ai indiqué ci-dessus) et que GC.SuppressFinalize () a été appelé, le Finalizer ne sera PAS appelé. C'est l'une des grandes raisons de mettre correctement en œuvre le modèle correctement. Puisqu'il n'y a qu'un seul thread Finalizer par processus géré, quel que soit le nombre de processeurs logiques, vous pouvez facilement dégrader les performances en sauvegardant ou même en suspendant le thread Finalizer en oubliant d'appeler GC.SuppressFinalize ().

J'ai publié une implémentation correcte du modèle Dispose sur mon blog: Comment implémenter correctement le modèle Dispose

Dave Black
la source
2
Êtes-vous sûr d'écrire NoGateway = new NoGateway();et NoGateway != null?
Cœur
1
S'agissait-il de stackoverflow.com/a/898856/3195477 ? Il n'y a pas de réponse maintenant affichée par le nom 'Icey'
UuDdLrLrSs
@DaveInCaz on dirait que c'est correct. Je ne vois «Icey» nulle part, mais le contexte de ma réponse semble être orienté vers la réponse fournie par votre lien ci-dessus. Peut-être qu'il a changé son nom d'utilisateur?
Dave Black
@DaveBlack cool, merci. Je viens de le modifier directement dans le texte.
UuDdLrLrSs
2

1) WebClient est un type géré, vous n'avez donc pas besoin d'un finaliseur. Le finaliseur est nécessaire dans le cas où vos utilisateurs ne suppriment pas () de votre classe NoGateway et le type natif (qui n'est pas collecté par le GC) doit être nettoyé après. Dans ce cas, si l'utilisateur n'appelle pas Dispose (), le WebClient contenu sera supprimé par le GC juste après le NoGateway.

2) Indirectement oui, mais vous ne devriez pas vous en soucier. Votre code est correct en l'état et vous ne pouvez pas empêcher vos utilisateurs d'oublier de Dispose () très facilement.

Jesse C. Slicer
la source
2

Modèle de msdn

public class BaseResource: IDisposable
{
   private IntPtr handle;
   private Component Components;
   private bool disposed = false;
   public BaseResource()
   {
   }
   public void Dispose()
   {
      Dispose(true);      
      GC.SuppressFinalize(this);
   }
   protected virtual void Dispose(bool disposing)
   {
      if(!this.disposed)
      {        
         if(disposing)
         {
            Components.Dispose();
         }         
         CloseHandle(handle);
         handle = IntPtr.Zero;
       }
      disposed = true;         
   }
   ~BaseResource()      
   {      Dispose(false);
   }
   public void DoSomething()
   {
      if(this.disposed)
      {
         throw new ObjectDisposedException();
      }
   }
}
public class MyResourceWrapper: BaseResource
{
   private ManagedResource addedManaged;
   private NativeResource addedNative;
   private bool disposed = false;
   public MyResourceWrapper()
   {
   }
   protected override void Dispose(bool disposing)
   {
      if(!this.disposed)
      {
         try
         {
            if(disposing)
            {             
               addedManaged.Dispose();         
            }
            CloseHandle(addedNative);
            this.disposed = true;
         }
         finally
         {
            base.Dispose(disposing);
         }
      }
   }
}
devnull
la source
1
using(NoGateway objNoGateway = new NoGateway())

est équivalent à

try
{
    NoGateway = new NoGateway();
}

finally
{
    NoGateway.Dispose();
}

Un finaliseur est appelé lorsque le GC détruit votre objet. Cela peut être à un moment totalement différent de celui où vous quittez votre méthode. Le Dispose of IDisposable est appelé immédiatement après avoir quitté le bloc using. Par conséquent, le modèle est généralement utilisé pour utiliser pour libérer des ressources immédiatement après que vous n'en ayez plus besoin.

Daniel Fabian
la source
1
Un finaliseur n'est pas appelé par le GC à détruire l'objet. Si "Finalize" est annulé, alors lorsque le GC aurait détruit l'objet , il sera placé dans une file d'attente d'objets à finaliser, créant temporairement une référence forte à lui et - au moins temporairement - "ressuscitant".
supercat
-5

D'après ce que je sais, il est fortement recommandé de ne PAS utiliser le Finalizer / Destructor:

public ~MyClass() {
  //dont use this
}

Surtout, cela est dû au fait de ne pas savoir quand ni SI il sera appelé. La méthode d'élimination est bien meilleure, surtout si vous nous utilisez ou éliminez directement.

l'utilisation est bonne. utilise le :)

Nic Wise
la source
2
Vous devez suivre le lien dans la réponse de thecoop. Oui, l'utilisation de / Dispose est meilleure, mais une classe jetable devrait certainement implémenter les deux.
Henk Holterman
Intéressant, tous les documents que j'ai lus sur Microsoft - par exemple les directives de conception de framework - disent de ne JAMAIS utiliser de destructeur. Utilisez toujours IDisposable.
Nic Wise
5
Il suffit de faire la distinction entre utiliser une classe et écrire la classe, puis relisez-les.
Henk Holterman
stackoverflow.com/questions/2605412/… peut aider
Alex Nolasco