J'ai besoin de conseils sur la mise en œuvre de la méthode Dispose
.
Dans notre application, l'utilisateur conçoit sa propre interface utilisateur. J'ai une fenêtre d'aperçu qui montre à quoi ressemblera l'interface utilisateur. Tous les objets dessinés dans cette interface utilisateur proviennent en définitive d'une classe de base commune ScreenObject. Mon gestionnaire d'aperçu contient une seule référence à un objet ScreenGrid, qui est l'objet de la grille pour toute la zone d'aperçu.
Question 1
Certaines de mes classes d'écran dérivées conservent des ressources non gérées, telles qu'une connexion à une base de données, une image bitmap et un contrôle WebBrowser
. Ces classes doivent disposer de ces objets. J'ai créé une méthode Dispose
virtuelle dans la classe de base base ScreenObject
, puis j'ai implémenté une méthode de substitution Dispose
dans chacune des classes dérivées qui contiennent des ressources non gérées. Cependant, à l'heure actuelle, je viens de créer une méthode appelée Dispose
, je n'implémente pas IDisposable
. Devrais-je implémenter IDisposable
? Si oui, comment puis-je le mettre en œuvre?
Est-il erroné de placer une méthode virtuelle Dispose
dans une classe de base dépourvue de ressources non gérées afin de pouvoir tirer parti du polymorphisme?
Question 2
En lisant à propos de la méthode Dispose
et de l’interface IDisposable
, Microsoft déclare que l’élimination doit uniquement appeler la méthode Dispose
pour son parent. Le parent l'appellera pour son parent et ainsi de suite. Pour moi, cela semble en arrière. Je veux peut-être me débarrasser d'un enfant mais garder son parent.
Je pense que ce devrait être l'inverse, un objet à éliminer devrait disposer de ses enfants. Les enfants doivent ensuite disposer de leurs enfants et ainsi de suite.
Est-ce que je me trompe ou est-ce que je manque quelque chose?
Question 1: Implémentez également IDisposable
en utilisant le modèle suivant:
public class MyClass : IDisposable
{
bool disposed;
protected virtual void Dispose(bool disposing)
{
if (!disposed)
{
if (disposing)
{
//dispose managed resources
}
}
//dispose unmanaged resources
disposed = true;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
Question 2: Microsoft veut dire qu'une classe dérivée appelle sa classe parente. Le propriétaire de l'instance appelle uniquement Dispose sur le type le plus dérivé.
Un exemple (abrégé):
class Parent : IDisposable
{
protected virtual void Dispose(bool disposing)
{
if (!disposed)
{
if (disposing)
{
//dispose managed resources
}
}
//dispose unmanaged resources
disposed = true;
}
}
class Child : Parent, IDisposable
{
protected override void Dispose(bool disposing)
{
if (!disposed)
{
if (disposing)
{
//dispose managed resources
}
base.Dispose(disposing);
}
//dispose unmanaged resources
disposed = true;
}
}
class Owner:IDisposable
{
Child child = new Child();
protected virtual void Dispose(bool disposing)
{
if (!disposed)
{
if (disposing)
{
if(child!=null)
{
child.Dispose();
}
}
}
//dispose unmanaged ressources
disposed = true;
}
}
Le propriétaire appelle uniquement Dispose
sur l'enfant, mais pas sur le parent. L’enfant est responsable d’appeler Dispose
sur le parent.
Selon les types d’objets que vous listez (base de données, WebBrowser, Bitmap, etc.), il s’agit de ressources gérées pour ce qui est de .NET. Par conséquent, vous devez implémenter IDisposable
sur toute classe ayant des types jetables en tant que membres. S'il s'agit d'instances déclarées localement, vous appelez simplement 'using ()' sur elles. Bien que ces instances que vous avez mentionnées contiennent des ressources non gérées, elles sont résumées par .NET grâce aux types que vous utilisez. Etant donné que vous utilisez uniquement des types gérés, vous devez implémenter IDisposable
mais sans finaliseur. Vous ne devez implémenter un finaliseur que si vous avez vraiment des ressources non gérées en tant que membres de classe.
Il semble que vous confondiez héritage (est a) avec agrégation/confinement (a). Par exemple, si "Container" contient une ressource jetable en tant que membre de la classe, cela s'appelle agrégation/confinement. Ainsi, l'appel de base.Dispose()
dans l'implémentation IDisposable
de Container n'a rien à voir avec la suppression de la ressource à usage unique dans de Container. Rappelez-vous que si une classe dérive de Container, dites "DerivedContainer", qu'elle est une instance de Container, mais avec des membres et/ou des fonctionnalités supplémentaires. Ainsi, toute instance de "DerivedContainer" a tous les membres de la classe de base "Container". Si vous n'avez jamais appelé base.Dispose()
, la ressource disponible dans "Container" ne serait pas libérée correctement (le GC le ferait en réalité, mais il est déconseillé de laisser ".NET s'en charger"). posted answer at Est-ce une mauvaise pratique de dépendre du ramasse-miettes automatisé .NET?.
Si vous n'appelez pas la classe de base Dispose()
, vous obtiendrez un objet partiellement supprimé (disposé dans la classe dérivée mais pas dans la classe de base) - un très mauvais scénario. Il est donc très important d'appeler la classe de base Dispose()
.
J'ai développé un modèle de pratiques recommandées (avec beaucoup d'expérience et de messages de débogage de la mémoire) écrit sur mon blog, à titre d'exemple. Il montre comment implémenter IDisposable
sur une classe de base et sur une classe dérivée:
http://dave-black.blogspot.com/2011/03/how-do-you-properly-implement.html
J'implémente IDisposable
class ConnectionConfiguration:IDisposable
{
private static volatile IConnection _rbMqconnection;
private static readonly object ConnectionLock = new object();
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!disposing)
{
return;
}
if (_rbMqconnection == null)
{
return;
}
lock (ConnectionLock)
{
if (_rbMqconnection == null)
{
return;
}
_rbMqconnection?.Dispose();//double check
_rbMqconnection = null;
}
}
}