Considérez le code suivant:
namespace DisposeTest
{
using System;
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Calling Test");
Test();
Console.WriteLine("Call to Test done");
}
static void Test()
{
DisposeImplementation di = new DisposeImplementation();
}
}
internal class DisposeImplementation : IDisposable
{
~DisposeImplementation()
{
Console.WriteLine("~ in DisposeImplementation instance called");
}
public void Dispose()
{
Console.WriteLine("Dispose in DisposeImplementation instance called");
}
}
}
Le Dispose n'est jamais appelé, même si je mets une boucle d'attente après l'invocation de Test();
. Donc ça craint. Je veux écrire une classe qui est simple et très facile à utiliser, pour m'assurer que toutes les ressources possibles sont nettoyées. Je ne veux pas donner cette responsabilité à l'utilisateur de ma classe.
Solution possible: utilisez using
, ou appelez Dispose moi-même (fondamentalement le même). Puis-je forcer l'utilisateur à utiliser une utilisation? Ou puis-je forcer l'appel à disposer?
L'appel de GC.Collect();
après Test();
ne fonctionne pas non plus.
Mettre di
dans null
n'invoque pas Dispose non plus. Le déconstructeur fonctionne, donc l'objet est déconstruit lorsqu'il quitte Test()
Ok les gars, c'est clair maintenant!
Merci à tous pour vos réponses! J'ajouterai un avertissement dans le commentaire!
Je veux écrire une classe qui est simple et très facile à utiliser, pour m'assurer que toutes les ressources possibles sont nettoyées. Je ne veux pas donner cette responsabilité à l'utilisateur de ma classe.
Tu ne peux pas faire ça. La gestion de la mémoire n'est tout simplement pas conçue pour accueillir des ressources qui ne sont pas spécifiquement de la mémoire.
Le modèle IDisposable est destiné aux développeurs comme un moyen de dire à un objet quand ils en ont fini, au lieu d'avoir la gestion de la mémoire essayant de comprendre cela en utilisant des choses comme le comptage de références.
Vous pouvez utiliser le Finalizer comme solution de rechange pour les utilisateurs qui ne parviennent pas à éliminer correctement les objets, mais il ne fonctionne pas bien comme méthode principale de nettoyage des objets. Pour fonctionner correctement, les objets doivent être correctement éliminés, de sorte que le Finalizer plus coûteux n'a jamais besoin d'être appelé.
Il convient de souligner deux points importants pour répondre à la question du PO:
using()
.Finalizer: Certains développeurs l'appellent un destructeur. Et en fait, il est même appelé un destructeur dans les versions C # 4.0 Language Spec (section 1.6.7.6) et précédent du courant spécification ECMA-334. Heureusement, la 4e édition (juin 2006) définit correctement les finaliseurs dans la section 8.7.9 et tente de dissiper la confusion entre les deux dans la section 17.12. Il convient de noter qu'il existe d'importantes différences internes (pas besoin d'entrer dans ces détails sanglants ici) entre ce qui est traditionnellement appelé destructeur et destructeur/finaliseur dans le .NET Framework.
GC.SuppressFinalize()
n'est pas appelé.GC.Collect(2)
pour la 2ème génération du GC.Finalisation: La finalisation est le moyen du .NET Framework pour gérer le nettoyage et la libération "gracieux" des ressources.
Bien que ce soit certainement plus d'informations que vous avez demandées, cela fournit des informations sur la façon dont les choses fonctionnent et pourquoi elles fonctionnent comme elles le font. Certaines personnes diront qu'elles ne devraient pas avoir à se soucier de la gestion de la mémoire et des ressources dans .NET, mais cela ne change pas le fait que cela doit être fait - et je ne vois pas cela disparaître dans un avenir proche.
Toutes les réponses sont (plus ou moins) correctes, voici un exemple:
static void Test()
{
using (DisposeImplementation di = new DisposeImplementation())
{
// Do stuff with di
}
}
L'appel manuel de Dispose
fonctionnera également, mais l'avantage de l'instruction using
est que l'objet sera également supprimé lorsque vous quitterez le bloc de contrôle car une exception est levée.
Vous pouvez ajouter un finaliseur qui gère l'élimination des ressources au cas où quelqu'un "oublie" d'utiliser l'interface IDisposable:
public class DisposeImplementation : 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
}
~DisposeImplementation()
{
Dispose(false);
}
}
Voir cette question pour plus d'informations. Cependant, cela ne fait que compenser les personnes qui n'utilisent pas correctement votre classe :) Je vous suggère d'ajouter un gros appel de Debug.Fail()
au Finalizer, pour avertir le développeur de leur erreur.
Si vous choisissez d'implémenter le modèle, vous verrez que GC.Collect()
déclenchera l'élimination.
Utilisez-le comme modèle/modèle pour vos cours
public class MyClass : IDisposable
{
private bool disposed = false;
// 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.
private 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.
......
}
// Call the appropriate methods to clean up
// unmanaged resources here.
// If disposing is false,
// only the following code is executed.
...........................
// Note disposing has been done.
disposed = true;
}
}
// 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.
~MyClass()
{
// Do not re-create Dispose clean-up code here.
// Calling Dispose(false) is optimal in terms of
// readability and maintainability.
Dispose(false);
}
}
Et bien sûr, comme mentionné par d'autres, n'oubliez pas le bloc using(...){}
.
Vous devrez appeler Dispose
explicitement ou en encapsulant l'objet dans une instruction using
. Exemple:
using (var di = new DisposeImplementation())
{
}
Solution possible: utilisez using ou appelez Dispose yourself (fondamentalement la même).
Utiliser using
équivaut à appeler Dispose
dans un bloc finally
.
Vous êtes censé en disposer vous-même, en appelant la méthode Dispose
ou en utilisant using
. Rappelez-vous, ce n'est pas un déconstructeur!
Si vous ne pouvez pas faire confiance aux utilisateurs de votre classe pour disposer correctement des ressources, ils vont probablement gâcher d'autres façons de toute façon.
Dispose n'est pas appelé automatiquement. Vous devez utiliser une clause using
pour encapsuler l'utilisation ou l'appeler manuellement.
Voir http://msdn.Microsoft.com/en-us/library/aa664736%28VS.71%29.aspx
Et juste pour anticiper une autre idée que vous pourriez avoir: vous ne pouvez pas appeler dispose
depuis le destructeur ... J'ai essayé cela il y a quelque temps dans un projet.