Est-il possible d'appeler une méthode virtuelle à partir de Dispose ou d'un destructeur?

StackOverflow https://stackoverflow.com/questions/137621

  •  02-07-2019
  •  | 
  •  

Question

Je ne trouve pas de référence, mais je me souviens avoir lu que ce n'était pas une bonne idée d'appeler des méthodes virtuelles (polymorphes) dans un destructeur ou la méthode Dispose () d'IDisposable.

Est-ce vrai et si oui, quelqu'un peut-il expliquer pourquoi?

Était-ce utile?

La solution

L'appel de méthodes virtuelles à partir d'un finaliseur / Dispose est dangereux, pour les mêmes raisons il est dangereux de le faire dans un constructeur . Il est impossible de s'assurer que la classe dérivée n'a pas déjà nettoyé certains états nécessaires à l'exécution correcte de la méthode virtuelle.

Certaines personnes sont désorientées par le modèle standard Jetable et son utilisation d'une méthode virtuelle, virtual Dispose (bool disposing) , et pensent que cela rend ok d'utiliser any méthode virtuelle lors d'une disposition. Considérez le code suivant:

class C : IDisposable {
    private IDisposable.Dispose() {
        this.Dispose(true);
    }
    protected virtual Dispose(bool disposing) {
        this.DoSomething();
    }

    protected virtual void DoSomething() {  }
}
class D : C {
    IDisposable X;

    protected override Dispose(bool disposing) {
        X.Dispose();
        base.Dispose(disposing);
    }

    protected override void DoSomething() {
        X.Whatever();
    }
}

Voici ce qui se passe lorsque vous supprimez un objet de type D , appelé d :

  1. Certains appels de code ((IDisposable) d) .Dispose ()
  2. C.IDisposable.Dispose () appelle la méthode virtuelle D.Dispose (bool)
  3. D.Dispose (bool) supprime D.X
  4. D.Dispose (bool) appelle C.Dispose (bool) de manière statique (la cible de l'appel est connue à temps de compilation )
  5. C.Dispose (bool) appelle les méthodes virtuelles D.DoSomething ()
  6. D.DoSomething appelle la méthode D.X.Wther () sur le D.X déjà disposé
  7. ?

Maintenant, la plupart des personnes qui exécutent ce code font quelque chose pour le réparer: elles déplacent l'appel base.Dispose (dispose) avant de nettoyer leur propre objet. Et oui, ça marche. Mais faites-vous vraiment confiance à Programmer X, développeur Ultra-Junior de la société pour laquelle vous avez développé C , affecté à l'écriture D , pour l'écrire de manière à ce que l'erreur soit soit détecté, soit l’appel base.Dispose (élimination) au bon endroit?

Je ne dis pas que vous ne devriez jamais, jamais écrire du code qui appelle une méthode virtuelle à partir de Dispose, mais que vous devez documenter le de cette méthode virtuelle. > condition qu'il n'utilise jamais aucun état défini dans une classe dérivée sous C .

Autres conseils

Les méthodes virtuelles sont découragées dans les constructeurs et les destructeurs.

La raison est plus pratique qu'autre chose: les méthodes virtuelles peuvent être remplacées de n'importe quelle manière choisie par le surcroqueur, et des choses telles que l'initialisation de l'objet lors de la construction, par exemple, doivent être assurées pour éviter de vous retrouver avec un objet ayant des valeurs NULL aléatoires et un état non valide.

Je ne crois pas qu'il y ait de recommandation contre l'appel de méthodes virtuelles. L’interdiction que vous vous souvenez peut être la règle contre le référencement des objets gérés dans le finaliseur.

Un modèle standard définit la documentation .Net expliquant comment Dispose () doit être implémenté. Le modèle est très bien conçu et doit être suivi de près.

L'essentiel est le suivant: Dispose () est une méthode non virtuelle qui appelle une méthode virtuelle Dispose (bool). Le paramètre boolean indique si la méthode est appelée à partir de Dispose () (true) ou du destructeur de l'objet (false). À chaque niveau d'héritage, la méthode Dispose (bool) doit être implémentée pour gérer tout nettoyage.

Lorsque la valeur false est transmise à Dispose (bool), cela signifie que le finaliseur a appelé la méthode dispose. Dans ce cas, seul le nettoyage des objets non gérés doit être tenté (sauf dans de rares cas). La raison en est que le ramasse-miettes vient d'appeler la méthode finalize. Par conséquent, l'objet en cours doit avoir été marqué comme étant prêt pour la finalisation. Par conséquent, tout objet auquel il fait référence peut également avoir été marqué comme étant lu pour la finalisation et, puisque la séquence est non déterministe, la finalisation peut déjà avoir eu lieu.

Je recommande vivement de rechercher le modèle Dispose () dans la documentation .Net et de le suivre à la lettre, car il vous protégera probablement contre les bogues difficiles et bizarres!

Pour développer la réponse de Jon, au lieu d'appeler des méthodes virtuelles, vous devez redéfinir la disposition ou le destructeur des sous-classes si vous devez gérer des ressources à ce niveau.

Bien que, je ne pense pas qu'il existe une "règle" en ce qui concerne le comportement ici. Mais l’idée générale est que vous voulez isoler le nettoyage des ressources uniquement pour cette instance à ce niveau d’implémentation.

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