135 votes

Comment empêcher IDisposable de se propager à toutes vos classes?

Commencez avec ces simples classes...

Disons que j'ai un simple ensemble de classes comme ceci:

class Bus
{
    Driver busDriver = new Driver();
}

class Driver
{
    Shoe[] shoes = { new Shoe(), new Shoe() };
}

class Shoe
{
    Shoelace lace = new Shoelace();
}

class Shoelace
{
    bool tied = false;
}

Un Bus a Driver, Driver deux Shoes, chaque Shoe a Shoelace. Tous très bête.

Ajouter un IDisposable objet à Lacet

Plus tard, j'ai décider que certaines opérations sur l' Shoelace pourraient être multi-thread, j'ai donc ajouter un EventWaitHandle pour les filets de communiquer avec. Donc, Shoelace ressemble maintenant à ceci:

class Shoelace
{
    private AutoResetEvent waitHandle = new AutoResetEvent(false);
    bool tied = false;
    // ... other stuff ..
}

Mettre en œuvre IDisposable sur Lacet

Buit maintenant FxCop va se plaindre: "mettre en Œuvre IDisposable sur "Lacet" parce qu'il crée des membres de la suite de IDisposable types: 'EventWaitHandle'."

Okay, je vais mettre en oeuvre IDisposable sur Shoelace et ma jolie petite classe devient cette horrible gâchis:

class Shoelace : IDisposable
{
    private AutoResetEvent waitHandle = new AutoResetEvent(false);
    bool tied = false;
    private bool disposed = false;

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

    ~Shoelace()
    {
        Dispose(false);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (!this.disposed)
        {
            if (disposing)
            {
                if (waitHandle != null)
                {
                    waitHandle.Close();
                    waitHandle = null;
                }
            }
            // No unmanaged resources to release otherwise they'd go here.
        }
        disposed = true;
    }
}

Ou (comme l'a souligné par les commentateurs) depuis Shoelace lui-même n'a pas de ressources non managées, je pourrais utiliser la plus simple de disposer de la mise en œuvre sans avoir besoin de l' Dispose(bool) et Destructeur:

class Shoelace : IDisposable
{
    private AutoResetEvent waitHandle = new AutoResetEvent(false);
    bool tied = false;

    public void Dispose()
    {
        if (waitHandle != null)
        {
            waitHandle.Close();
            waitHandle = null;
        }
        GC.SuppressFinalize(this);
    }
}

Regarde avec horreur que IDisposable se propage

Droit qui est fixe. Mais maintenant, FxCop va se plaindre qu' Shoe crée un Shoelace, alors Shoe doit IDisposable trop.

Et Driver crée Shoe donc Driver doit IDisposable. et Bus crée Driver donc Bus doit IDisposable et ainsi de suite.

Tout à coup, mon petit changement d' Shoelace est à l'origine de beaucoup de travail et mon patron est de se demander pourquoi j'ai besoin de checkout Bus de faire un changement d' Shoelace.

La Question

Comment voulez-vous empêcher cette propagation de l' IDisposable, mais encore de s'assurer que vos objets managés sont correctement éliminés?

34voto

Grzenio Points 16802

Vous ne pouvez pas vraiment de "prévenir" IDisposable de se propager. Certaines classes doivent être éliminés, comme AutoResetEvent, et le moyen le plus efficace est de le faire dans l' Dispose() méthode pour éviter la surcharge des finaliseurs. Mais cette méthode doit être appelée en quelque sorte, donc exactement comme dans votre exemple, les classes qui encapsulent ou contenir IDisposable avoir de disposer de ces, de sorte qu'ils doivent être à usage unique, etc. La seule façon de l'éviter est de:

  • évitez d'utiliser des IDisposable classes, si possible, de verrouillage ou d'attendre les événements dans une seule des lieux, gardez cher ressources dans un seul endroit, etc
  • créer uniquement lorsque vous en avez besoin et de les disposer juste après ( using modèle)

Dans certains cas, IDisposable peut être ignoré, car il prend en charge une option de cas. Par exemple, WaitHandle implémente IDisposable à l'appui d'un Mutex nommé. Si un nom n'est pas utilisé, la méthode dispose ne fait rien. MemoryStream est un autre exemple, il n'utilise pas les ressources du système et de Disposer de son œuvre, ne fait rien. Une réflexion approfondie sur que non géré ressource est utilisée ou non, peut être pédagogique. Donc peut examiner les sources disponibles pour l' .net des bibliothèques ou à l'aide d'un décompilateur.

19voto

JaredPar Points 333733

En termes d'exactitude, vous ne pouvez pas éviter la propagation de IDisposable à travers une relation de l'objet si un objet parent crée et essentiellement possède un objet enfant qui doit maintenant être jetables. FxCop est correct dans cette situation et le parent doivent être IDisposable.

Ce que vous pouvez faire est d'éviter l'ajout d'un IDisposable à une classe de feuilles dans votre hiérarchie d'objets. Ce n'est pas toujours une tâche facile, mais c'est un exercice intéressant. À partir d'un point de vue logique, il n'y a pas de raison qu'un Lacet de chaussure doit être à usage unique. Au lieu d'ajouter un WaitHandle ici, est-il possible d'ajouter une association entre un Lacet de chaussure et un WaitHandle au point qu'il est utilisé. La façon la plus simple est par le biais d'un Dictionnaire de l'instance.

Si vous pouvez déplacer le WaitHandle dans une association libre par l'intermédiaire d'une carte à l'endroit où le WaitHandle est effectivement utilisé, alors vous pouvez briser cette chaîne.

14voto

Jordão Points 29221

Pour prévenir IDisposable de diffusion, vous devriez essayer d'encapsuler l'utilisation d'un objet jetable à l'intérieur d'une méthode unique. Essayez de concevoir Shoelace différemment:

class Shoelace { 
  bool tied = false; 

  public void Tie() {

    using (var waitHandle = new AutoResetEvent(false)) {

      // you can even pass the disposable to other methods
      OtherMethod(waitHandle);

      // or hold it in a field (but FxCop will complain that your class is not disposable),
      // as long as you take control of its lifecycle
      _waitHandle = waitHandle;
      OtherMethodThatUsesTheWaitHandleFromTheField();

    } 

  }
} 

Le champ d'application de l'attente de la poignée est limitée à l' Tieméthode, et la classe n'a pas besoin d'avoir un revenu champ, et donc n'aura pas besoin d'être à usage unique.

Depuis l'attente de la poignée est un détail d'implémentation à l'intérieur de l' Shoelace, il ne devrait pas changer son interface publique, comme l'ajout d'une nouvelle interface dans sa déclaration. Ce qui va se passer ensuite, lorsque vous n'avez pas besoin d'un revenu champ plus, allez-vous supprimer l' IDisposable déclaration? Si vous pensez à l' Shoelace d'abstraction, vous vous apercevrez rapidement qu'il ne devrait pas être pollué par les dépendances à l'infrastructure, à l'instar IDisposable. IDisposable devrait être réservé pour des cours dont l'abstraction encapsuler une ressource qui appelle pour déterministe nettoyer; c'est à dire, pour les classes où le tout-jetable partie de l' abstraction.

3voto

Adam Points 206

Cela ressemble beaucoup à un problème de conception de haut niveau, comme c'est souvent le cas lorsqu'un «correctif rapide» se transforme en un bourbier. Pour plus d'informations sur les moyens de sortir, vous trouverez peut-être ce sujet utile.

3voto

GrahamS Points 3315

Il est intéressant de noter si Driver est défini comme ci-dessus:

class Driver
{
    Shoe[] shoes = { new Shoe(), new Shoe() };
}

Puis, quand Shoe est effectué IDisposable, FxCop (v1.36) ne se plaignent pas qu' Driver doit également être IDisposable.

Toutefois, si elle est définie comme ceci:

class Driver
{
    Shoe leftShoe = new Shoe();
    Shoe rightShoe = new Shoe();
}

puis il va se plaindre.

Je pense que c'est juste une limitation de FxCop, plutôt qu'à une solution, parce que dans la première version de l' Shoe cas sont encore en cours de création par l' Driver et doivent encore être éliminés en quelque sorte.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X