2009-11-09 9 views
7

J'ai une classe abstraite qui implémente IDisposable, comme ceci:C# abstraite méthode Dispose

public abstract class ConnectionAccessor : IDisposable 
{ 
    public abstract void Dispose(); 
} 

Dans Visual Studio 2008 Team System, j'ai couru l'analyse du code sur mon projet et l'une des mises en garde qui est venu était le suivant:

Microsoft.Design: Modifier 'ConnectionAccessor.Dispose() de sorte qu'il appelle Dispose (vrai), puis appelle GC.SuppressFinalize sur l'instance actuelle de l'objet ('ceci' ou 'moi' dans Visual Basic), puis revient.

Est-il juste être stupide, me disant de modifier le corps d'une méthode abstraite, ou devrais-je faire quelque chose de plus dans une instance dérivée de Dispose?

+0

Pourquoi avez-vous besoin d'ajouter Dispose() à votre interface? Si elle est héritée de IDisposable, la méthode Dispose() fait déjà partie de votre interface. –

+0

Seth, vous devez implémenter tous les membres de l'interface. Le laisser dehors ne compilerait pas. –

Répondre

12

Vous devez suivre le schéma classique pour la mise en œuvre Dispose. Rendre Dispose() virtuel est considéré comme une mauvaise pratique, car le modèle conventionnel met l'accent sur la réutilisation du code dans le "nettoyage géré" (appel client Dispose() directement ou via using) et "nettoyage non géré" (GC appel finalizer). Pour rappel, le modèle est le suivant:

public class Base 
{ 
    ~Base() 
    { 
     Dispose(false); 
    } 

    public void Dispose() 
    { 
     Dispose(true); 
     GC.SuppressFinalize(this); // so that Dispose(false) isn't called later 
    } 

    protected virtual void Dispose(bool disposing) 
    { 
     if (disposing) 
     { 
      // Dispose all owned managed objects 
     } 

     // Release unmanaged resources 
    } 
} 

clé ici est qu'il n'y a pas de double emploi entre finaliseur et Dispose pour le nettoyage non géré, mais une classe dérivée peut étendre à la fois le nettoyage géré et non géré.

Pour votre cas, ce que vous devez faire est la suivante:

protected abstract void Dispose(bool disposing) 

et laisser tout le reste est. Même cela est d'une valeur douteuse, puisque vous appliquez vos classes dérivées pour implémenter Dispose maintenant - et comment savez-vous que tous en ont besoin? Si votre classe de base n'a rien à éliminer, mais que la plupart des classes dérivées le font (à quelques exceptions près, peut-être), fournissez simplement une implémentation vide. C'est ce que fait System.IO.Stream (lui-même abstrait), donc il y a un précédent.

+0

Je préfère que les gens ont été invités à le faire: http://nitoprograms.blogspot.com/2009/08/how-to-implement-idisposable-and.html - le modèle serait beaucoup plus simple si il n'a pas intégré la prise en charge des classes qui mélangent des ressources gérées et non gérées. –

+0

Si une classe dérivée d'une classe particulière doit implémenter IDisposable, et si un objet qui s'attend à recevoir un type de classe de base peut finir par contenir la dernière référence utile, la classe de base doit implémenter IDisposable, même si la grande majorité des classes dérivées n'en auront pas besoin. – supercat

+0

Ce modèle n'est-il pas dépassé depuis .net 2? Ma compréhension est que les ressources non managées doivent être tenues par une forme de poignée de sécurité/critique et les classes implémentées par l'utilisateur normal n'ont plus besoin d'un finaliseur. Et leur 'Dispose' appelle juste' Dispose' sur le handle. – CodesInChaos

10

L'avertissement vous indique fondamentalement d'implémenter le Dispose pattern dans votre classe.

Le code résultant devrait ressembler à ceci:

public abstract class ConnectionAccessor : IDisposable 
{ 
    ~ConnectionAccessor() 
    { 
     Dispose(false); 
    } 

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

    protected virtual void Dispose(bool disposing) 
    { 
    } 
} 
+3

+1 pour mentionner le motif, et en soulignant que Dispose (bool) devrait être la méthode virtuelle/abstraite que les classes dérivées devraient implémenter ou remplacer, pas Dispose(). – Yoopergeek

-1

L'avertissement est intéressant cependant. Eric Lippert, l'un des concepteurs de C#, a écrit un blog sur la raison pour laquelle les messages d'erreur devraient être «Diagnostique mais pas prescriptif: décrivez le problème, pas la solution». Read here.

+1

Ce n'est pas un message d'erreur du compilateur. C'est le résultat de l'exécution d'un outil qui pointe spécifiquement les choses problématiques. Il n'y a rien de mal à le signaler également la solution. –

+0

Bien sûr. Je voulais juste partager le lien. Cela m'est venu parce que le message n'énonce pas le problème mais donne la solution seulement. Espérons qu'il y ait eu un message précédent le faisant. –

+0

C'est un peu tangent à la question originale. –

1

Bien que cela ressemble un peu à du nit-picking, le conseil est valable. Vous indiquez déjà que vous vous attendez à ce que tous les sous-types de ConnectionAccessor aient quelque chose dont ils doivent disposer. Par conséquent, il semble préférable de s'assurer que le nettoyage correct est effectué (en termes d'appel GC.SuppressFinalize) par la classe de base plutôt que de dépendre de chaque sous-type pour le faire.

J'utilise le motif Éliminez mentionné dans le livre Bruce Wagners Effective C# qui est essentiellement:

public class BaseClass : IDisposable 
{ 
    private bool _disposed = false; 
    ~BaseClass() 
    { 
     Dispose(false); 
    } 

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

    protected virtual void Dispose(bool disposing) 
    { 
     if (_disposed) 
      return; 

     if (disposing) 
     { 
      //release managed resources 
     } 

     //release unmanaged resources 

     _disposed = true; 
    } 
} 

public void Derived : BaseClass 
{ 
    private bool _disposed = false; 

    protected override void Dispose(bool disposing) 
    { 
     if (_disposed) 
      return; 

     if (disposing) 
     { 
      //release managed resources 
     } 

     //release unmanaged resources 

     base.Dispose(disposing); 
     _disposed = true; 
    } 
3

Le seul petit reproche que j'aurais avec les réponses fournies à ce jour est que tous supposent que vous besoin de avoir un finalisateur, ce qui n'est pas forcément le cas. Il y a un surcoût de performance assez important associé à la finalisation, que je ne voudrais pas imposer à toutes mes classes dérivées si ce n'était pas nécessaire. Voir this blog post par Joe Duffy, qui explique quand vous pouvez ou non avoir besoin d'un finaliseur, et comment implémenter correctement le pattern Dispose dans les deux cas.
Résumant l'article du blog de Joe, à moins que vous ne travailliez sur quelque chose de relativement bas traitant de la mémoire non gérée, vous ne devriez pas implémenter un finalizer. En règle générale, si votre classe ne contient que des références aux types gérés qui implémentent IDisposable eux-mêmes, vous n'avez pas besoin du finaliseur (mais devez implémenter IDisposable et disposer de ces ressources). Si vous allouez des ressources non managées directement à partir de votre code (PInvoke?) Et que ces ressources doivent être libérées, vous en avez besoin. Une classe dérivée peut toujours ajouter un finaliseur si elle en a vraiment besoin, mais forcer toutes les classes dérivées à avoir un finaliseur en la plaçant dans la classe de base provoque l'impact de toutes les classes dérivées sur la performance des objets finalisables. nécessaire.

+0

Mon reproche préféré. La plupart des gens ne prennent pas la peine d'essayer de comprendre cela, et le meilleur conseil qu'ils ont tendance à obtenir est de simplement implémenter le modèle de Microsoft. Voici ce que je pense est un bien meilleur conseil que le modèle "officiel": http://nitoprograms.blogspot.com/2009/08/how-to-implement-idisposable-and.html –

+0

C'est en effet un bon blog sur le sujet - simple et aussi le point. –

Questions connexes