Question

J'ai une classe abstraite qui implémente IDisposable, comme suit:

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 ont été soulevés é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 retourne .

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?

Était-ce utile?

La solution

Vous devez suivre le modèle classique pour la mise en œuvre Dispose. Faire Dispose() virtuelle est considéré comme une mauvaise pratique, parce que le modèle classique met l'accent sur la réutilisation de code dans « nettoyage géré » (API client appelant Dispose() directement ou via using) et « nettoyage non géré » (GC appelant finaliseur). Pour rappel, le schéma 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
    }
}

est ici 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 appliquer vos classes dérivées pour mettre en œuvre Dispose maintenant - et comment savez-vous que tous en ont besoin? Si votre classe de base n'a rien à disposer, mais les classes les plus dérivées font probablement (à quelques exceptions près, peut-être), puis juste fournir une implémentation vide. Il est ce que System.IO.Stream (lui-même abstrait) le fait, donc il existe un précédent.

Autres conseils

L'avertissement vous indique essentiellement à mettre en œuvre le Éliminer modèle 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)
    {
    }
}

Le seul petit reproche que j'aurais avec les réponses fournies à ce jour est que tous supposent que vous besoin pour avoir un finaliseur, ce qui est pas nécessairement le cas. Il y a une surcharge de performances assez importants associés à la finalisation, que je ne voudrais pas imposer à toutes mes classes dérivées si elle n'a pas été nécessaire.

Voir ce blog par Joe Duffy, ce qui explique quand vous pouvez ou non besoin d'un finaliseur, et la manière d'implémenter le modèle Dispose dans les deux cas.
Résumant le blog de Joe, à moins que vous faites quelque chose d'assez bas niveau traitant de la mémoire non géré, vous ne devriez pas mettre en œuvre un finaliseur. En règle générale, si votre classe ne détient que des références à des types gérés qui implémentent IDisposable eux-mêmes, vous n'avez pas besoin du finaliseur (mais devrait mettre en œuvre IDisposable et de disposer de ces ressources). Si vous allouez les ressources non gérés directement à partir de votre code (PInvoke?) Et ces ressources doivent être libérées, vous en avez besoin. Une classe dérivée peut toujours ajouter un finaliseur si elle a vraiment besoin, mais forçant toutes les classes dérivées d'avoir un finaliseur en le plaçant dans la classe de base provoque des classes toutes dérivées d'être affectées par la baisse de performance des objets finalisables lorsque que les frais généraux ne peut pas être nécessaire.

Bien qu'il ne semble un peu comme tatillonne, le conseil est valide. Vous indiquez déjà que vous attendre à des sous-types de ConnectionAccessor auront quelque chose dont ils ont besoin de disposer. Il ne semble donc préférable de faire en sorte que le nettoyage approprié est fait (en termes de l'appel GC.SuppressFinalize) par la classe de base plutôt que de compter sur chaque sous-type pour le faire.

J'utilise le motif mentionné dans le livre Éliminez Bruce Wagners efficace 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;
    }

L'avertissement est intéressant cependant. Eric Lippert, l'un des concepteurs de C #, blogué sur pourquoi les messages d'erreur doivent être « diagnostic mais pas prescriptive: décrire le problème, pas la solution ». Lire ici .

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top