3
votes

Est-il vraiment nécessaire d'implémenter un modèle d'élimination pour les ressources gérées uniquement?

J'ai lu attentivement ceci un> article et il semble clairement indiquer que le modèle de disposition doit être implémenté dans tous les cas d'implémentation IDisposable . J'essaie de comprendre pourquoi j'ai besoin d'implémenter le modèle de disposition dans les cas où ma classe ne contient que des ressources gérées (c'est-à-dire d'autres membres IDisposable ou des poignées sûres). Pourquoi ne puis-je pas simplement écrire

class Foo : IDisposable
{
    IDisposable boo;

    public virtual void Dispose()
    {
        boo?.Dispose();
    }
}

// child class which holds umanaged resources and implements dispose pattern
class Bar : Foo
{
    bool disposed;
    IntPtr unmanagedResource = IntPtr.Zero;

    ~Bar()
    {
        Dispose(false);
    }

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

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

        if (disposing)
        {
            // Free any other managed objects here.
            //
        }
        // close handle

        disposed = true;
    }
}

// another child class which doesn't hold unmanaged resources and merely uses Dispose 
class Far : Foo
{
    private IDisposable anotherDisposable;

    public override void Dispose()
    {
        base.Dispose();
        anotherDisposable?.Dispose();
    }
}

S'il est bien connu qu'il n'y a pas de ressources non gérées et qu'il est inutile d'appeler la méthode Dispose depuis le finaliseur car les ressources gérées ne le sont pas être libéré du finaliseur?

Mise à jour : afin d'ajouter un peu de clarté. La discussion semble se résumer à la question de savoir s'il est nécessaire d'implémenter le modèle de disposition pour chaque classe publique de base non scellée qui implémente IDisposable ou non. Cependant, je ne peux pas trouver de problèmes potentiels avec la hiérarchie lorsqu'une classe de base sans ressources non gérées n'utilise pas le modèle de disposition alors que les classes enfants qui ont des ressources non gérées utilisent ce modèle:

class Foo : IDisposable
{
    IDisposable boo;

    void Dispose()
    {
        boo?.Dispose();
    }
}

Encore plus , pour moi, cela semble être une meilleure séparation des préoccupations lorsque les implémentations ne sont responsables que des choses dont elles sont conscientes.


13 commentaires

Que faire si une classe dérivée ajoute des ressources non gérées?


Même s'il ne s'agit que de ressources gérées, les événements sont une source très courante de fuites de mémoire. Un événement ne peut être défini sur null que par le propriétaire.


@SLaks, alors pourquoi ne puis-je pas rendre la méthode Dispose virtuelle et implémenter le modèle de disposition dans la classe dérivée dans ce cas?


@ Zer0, les événements ne peuvent-ils pas être nuls dans la méthode publique Dispose sans utiliser Dispose (suppression des booléens)?


"Pourquoi ne puis-je pas simplement écrire ...", par opposition à quoi?


@quantificion vous pouvez et vous devez remplacer Dispose dans l'héritage des classes. Essayer de corriger un mauvais code qui fuit à travers des abonnements d'événements avec des finaliseurs est insensé (et rien ne garantit non plus qu'il fonctionne correctement car les finaliseurs pourraient être appelés beaucoup plus tard).


@quantification la citation dans ma réponse est tirée du début du même article. De plus, comme je l'ai mentionné, vous pouvez vérifier le modèle généré automatiquement (cliquez sur IDisposable dans votre code -> implémenter avec le modèle).


@ Sergey.quixoticaxis.Ivanov, alors l'article semble présenter des déclarations contradictoires car il y a une autre citation: Vous devez implémenter ce modèle pour toutes les classes de base qui implémentent Dispose () et ne sont pas scellées .


@quantificon cet article est malheureusement comme ça depuis des lustres, c'est probablement l'une des raisons pour lesquelles tant de code implémente des finaliseurs (avec une syntaxe de destructeur, oh) dans des classes qui n'ont rien à voir avec des ressources non gérées ou qui ont des ressources non gérées dans leurs héritiers. Je pense que le moyen le plus simple est de créer une application console avec trois classes A , B: A , C: B et Console .WriteLine partout. Comme je crois que lorsque la littérature semble contradictoire, il est toujours plus facile de s'essayer.


Étant donné que l'idée générale que les finaliseurs sont suffisants pour libérer des ressources non gérées était erronée, tout ce modèle est probablement une erreur. Donc, accueillir les finaliseurs selon un modèle aussi commun n'aurait jamais dû être fait. Je pense que l'introduction de dispose (bool) plus bas dans la hiérarchie des types fonctionnerait bien, et rétrospectivement, c'est probablement une meilleure conception.


@ Sergey.quixoticaxis.Ivanov, je l'ai essayé et je sais comment cela fonctionne, mais la principale raison pour laquelle j'ai soulevé la question est que disposer de l'applicabilité du modèle pour des cas spécifiques, j'ai souligné dans la question, semble commencer à avoir un raisonnement dogmatique et je ' J'essaie de clarifier s'il y a encore des raisons valables qui me manquent ou si nous devons admettre que ce n'est pas seulement une façon de couvrir des scénarios spécifiques.


@ LasseVågsætherKarlsen, par opposition à Vous devez implémenter ce modèle pour toutes les classes de base qui implémentent Dispose () et ne sont pas scellées , tirées de .


@quantificon la seule chose que je puisse dire à ce sujet est que dispose est actuellement utilisé pour 2 scénarios indépendants dans .Net: 1er scénario original de travail avec des ressources non gérées (ce que, comme je suppose d'après votre commentaire, vous avez compris) et 2ème scénario non conventionnel (mais largement utilisé) d'émulation de la sémantique du destructeur c ++. Le modèle de disposition est une solution définie pour le premier. C'est pourquoi la documentation est si concentrée sur les ressources non gérées. Le modèle de mise au rebut est applicable pour le scénario ultérieur, mais, pour autant que je sache et pour autant que je comprends la documentation, n'est pas destiné à cela.


4 Réponses :


0
votes

Vous vous êtes probablement trompé. Vous n'avez pas besoin d'implémenter finilizer si vous ne disposez pas de ressources non gérées. Vous pouvez le vérifier en utilisant l'implémentation automatique du modèle dans Visual Studio (il générera même les commentaires indiquant que vous devez décommenter le finaliseur UNIQUEMENT si vous utilisez des ressources non gérées).

Le modèle de disposition est utilisé uniquement pour les objets qui accèdent aux ressources non gérées.

Si vous concevez une classe de base et que certaines des classes héritées accèdent à des ressources non managées, les classes héritières la traiteront elles-mêmes en remplaçant Dispose (bool) et en définissant le finaliseur.

Il est expliqué dans ceci < / a> article, tous les finaliseurs seront de toute façon appelés s'ils ne sont pas supprimés. Et si supprimé, tout aurait été libéré par la chaîne d'appels Diapose (true) en premier lieu.


2 commentaires

J'ai pris ceci dans l'article, veuillez consulter ici , marqué comme important. Je ne sais pas comment cela peut être interprété autrement. I, btw ne signifiait pas spécifiquement Finaliser, mais disposer du tout ce qui peut également impliquer une implémentation sans finaliseur.


@quantificon la citation dans ma réponse est tirée du même article. De plus, comme je l'ai déjà mentionné, vérifiez la mise en œuvre automatique de VS.



5
votes

Ceci

   public  class Foo : IDisposable
    {
        IDisposable boo;

        public virtual void Dispose()
        {
            boo?.Dispose();
        }
    }
    public class Bar : Foo
    {
        IntPtr unmanagedResource = IntPtr.Zero;
        ~Bar()
        {
            this.Dispose();
        }

        public override void Dispose()
        {
            CloseHandle(unmanagedResource);
            base.Dispose();
        }

        void CloseHandle(IntPtr ptr)
        {
            //whatever
        }
    }

est parfaitement bien. Tel quel

public sealed class Foo : IDisposable
{
    IDisposable boo;

    public void Dispose()
    {
        boo?.Dispose();
    }
}

Qu'est-ce qui pourrait mal se passer si j'ai une classe de base publique non scellée implémentée comme ci-dessus avec la méthode Virtual Dispose?

À partir de la documentation a>:

Parce que l'ordre dans lequel le garbage collector détruit est géré objets lors de la finalisation n'est pas défini, appelant ce Dispose la surcharge avec une valeur de false empêche le finaliseur d'essayer de libérez les ressources gérées qui ont peut-être déjà été récupérées.

L'accès à un objet géré qui a déjà été récupéré ou l'accès à ses propriétés après sa suppression (peut-être par un autre finaliseur) entraînera la levée d'une exception dans le finaliseur, qui est mauvais :

Si Finalize ou un remplacement de Finalize lève une exception, et le runtime n'est pas hébergé par une application qui remplace la valeur par défaut politique, le runtime met fin au processus et aucun essai / enfin actif des blocs ou des finaliseurs sont exécutés. Ce comportement garantit le processus l'intégrité si le finaliseur ne peut pas libérer ou détruire des ressources.

Alors si vous aviez:

private class Foo : IDisposable
{
    IDisposable boo;

    public void Dispose()
    {
        boo?.Dispose();
    }
}

~ Bar -> Bar.Dispose () -> base.Dispose () -> boo.Dispose () Mais boo a peut-être été récupéré par le GC.


13 commentaires

juste pour ajouter à ceci: le modèle de suppression complet n'est applicable que si votre classe est directement responsable de toutes les ressources non gérées - alors vous auriez besoin d'un finaliseur pour les libérer au cas où quelqu'un oublierait d'appeler Dispose. Si votre classe n'a pas de ressources non gérées (ou si elle utilise SafeHandle pour les gérer), vous n'avez pas besoin d'un finaliseur et l'implémentation ci-dessus d'IDisposable est tout ce dont vous avez besoin.


@Brandon Ce que je crois que cette réponse implique, c'est qu'il est prudent de renoncer au modèle de suppression lorsque vous êtes sûr que la classe ne peut pas être héritée. Sinon, il est tout à fait plausible qu'il existe des ressources non gérées dans quelque chose qui dérive de votre classe. Dans ce cas, vous avez besoin du modèle de disposition (y compris les finaliseurs).


@ Zer0 non vous ne le faites pas, et vous pouvez facilement vérifier que vous ne le faites pas. Comme l'a dit Brandon, seules les classes qui sont directement responsables des ressources non gérées en ont besoin. Et bien sûr, vous pouvez facilement le vérifier.


@ Sergey.quixoticaxis.Ivanov Peut-être que vous m'avez mal compris. Vous n'avez pas besoin d'un finaliseur sur une classe sans ressources non gérées. Mais vous devez suivre le modèle d'élimination. Il n'y a pas de moyen facile (en dehors de la réflexion) qu'une classe puisse facilement détecter si les classes dérivées ont des ressources non managées. De plus, vous devriez suivre le modèle de disposition même sans ressources non gérées (les événements sont la première chose à penser).


Qu'est-ce qui pourrait mal tourner si j'ai une classe de base publique non scellée implémentée comme ci-dessus avec la méthode Virtual Dispose?


@ Zer0, Pourriez-vous expliquer pourquoi avoir Dispose (suppression des booléens) est important pour le nettoyage des événements?


@ Zer0 et il n'y a pas besoin de savoir. Si vous supprimez votre classe, la chaîne Dispose-> base.Dispose nettoiera tout. S'il s'agit de la finalisation, les finaliseurs seront appelés par CLR pour toutes les classes de la hiérarchie, nettoyant ainsi à nouveau tout.


@quantificon Je faisais référence à la partie virtuelle du modèle de disposition, pas au booléen indiquant s'il est en train de supprimer des ressources gérées ou non. Je posterai une réponse qui montre une mauvaise implémentation de Dispose que je vois trop souvent qui provoquera des fuites de mémoire.


Je ne peux toujours pas obtenir un point. Pourquoi une classe dérivée mal implémentée est une justification pour imposer le modèle de disposition dans une classe de base. Nous pouvons et devrions implémenter le modèle de disposition pour Bar car il contient des ressources non gérées, mais pourquoi devrait-il avoir une influence sur la façon dont Foo va être implémenté?


"pourquoi devrait-il avoir une influence sur la façon dont Foo va être implémenté?" Parce que lorsque vous écrivez une classe ouverte à l'extension, vous devez permettre d'étendre cette classe sans introduire de bogues.


Mais disposer le modèle dans la classe de base n'empêche pas réellement l'introduction de bogues dans une classe dérivée, il n'impose aucune restriction sur les implémentations des classes enfants. Si quelqu'un n'applique pas le modèle de disposition là où c'est nécessaire pour une raison quelconque, la probabilité qu'il / elle commence à le faire uniquement parce qu'il est appliqué dans la classe de base est assez similaire à la probabilité que cela ne se produise pas.


J'ai mis à jour ma question pour inclure l'exemple que vous avez donné avec des implémentations appropriées de Bar . Je suis toujours à la recherche d'un raisonnement solide pour ne pas faire les choses de cette façon et faire toujours comme l'article le prescrit.


Il y a une déclaration étrange dans l'article lié, qui semble être la racine de tout ce désordre: « Objets gérés qui consomment de grandes quantités de mémoire ou consomment des ressources limitées. Libérer ces objets explicitement dans la méthode Dispose les libère plus rapidement que s'ils étaient récupérés de manière non déterministe par le garbage collector ». Comment feriez-vous cela? L'appel de Dispose () n'est possible que si l'objet implémente IDisposable . Et la méthode IDisposable.Dispose () sert uniquement à libérer des ressources non gérées . La récupération des objets gérés tout en étant accessibles par le finaliseur est-elle vraiment possible?



-1
votes

Si Dispose () est implémenté en utilisant une méthode virtuelle publique, les classes dérivées qui s'attendent à remplacer cette méthode peuvent le faire et tout ira bien. Si, cependant, quelque chose sur la chaîne d'héritage implémente IDisposable.Dispose () via des moyens autres que de remplacer la méthode publique virtuelle Dispose () , cela peut rendre impossible pour un sous -dérivée de la classe pour implémenter son propre IDisposable.Dispose () tout en étant toujours en mesure d'accéder à l'implémentation parente.

Le modèle Dispose (bool) peut être utilisé indépendamment du fait qu'il existe ou non une méthode publique Dispose () , et évite ainsi le besoin d'avoir des modèles séparés pour les cas où une classe expose ou n'expose pas une méthode Dispose () publique. Le GC.SuppressFinalize (this) pourrait généralement être remplacé par un GC.KeepAlive (this) , mais pour les classes sans finaliseurs, le coût est à peu près le même. En l'absence de cet appel, les finaliseurs pour tous les objets auxquels une classe contient une référence pourraient être déclenchés pendant que la méthode Dispose de la classe est en cours d'exécution. Ce n'est pas un scénario probable, ni un scénario qui causerait généralement des problèmes même s'il se produit, mais en passant ceci à GC.KeepAlive (Object) ou GC.SuppressFinalize (Object ) rend ces situations bizarres impossibles.


6 commentaires

À mon humble avis, cela ne devrait pas être un problème, car les références ne sont pas destinées à être utilisées de toute façon: le finaliseur appelle Dispose (false) et il ne doit toucher que les ressources non gérées.


@ Sergey.quixoticaxis.Ivanov: Je ne sais pas ce que vous voulez dire. Les conditions de concurrence entre les finaliseurs et Dispose sont rares et ne causent généralement pas de problèmes, mais appeler une fonction qui est garantie pour empêcher la finalisation prématurée d'un objet et de tout ce à quoi il contient une référence est plus facile que d'essayer de le faire assurez-vous que ne pas le faire ne peut jamais causer de problèmes.


Je veux dire quand vous avez classe A {privé B b; private UnmanagedResource ur; } , si vous arrivez à Dispose (false) depuis le finaliseur, vous ne devez pas du tout accéder à b (nettoyer uniquement vos propres ressources non gérées dans A ), donc si b est déjà fini ou non n'a aucune importance. Peut-être ai-je mal compris ce que vous voulez dire.


@ Sergey.quixoticaxis.Ivanov: Étant donné quelque chose comme class wrapper {int resource; void closeResource () {apiCleanupFunction (ressource); void doSomething () {otherApiFunction (ressource); }} class myThing {l'enveloppe; void Dispose () {it.doSomething (); it.closeResource (); } s'il n'y avait pas de GC.KeepAlive () , n'importe où, le JIT pourrait remplacer myThing.Dispose par {int rsrc = it.resource; otherApiFunction (rsrc); someApiFunction (rsrc);} avec myThing devenant éligible pour finalize () dès que it.resource est copié dans rsrc , ce qui pourrait se produire avant la fin de otherApiFunction () .


@ Sergey.quixoticaxis.Ivanov: Si la classe wrapper est écrite correctement, elle doit inclure ses propres appels GC.KeepAlive () ou GC.SuppressFinalize () pour s'empêcher de en cours de finalisation avant le retour de l'exécution de closeResource () , mais s'il n'y a pas d'appels keep-alive n'importe où, des choses bizarres et farfelues peuvent arriver. Si la classe externe inclut un GC.SuppressFinalize () et que sa méthode Dispose est appelée, cela évitera ces problèmes même si la classe interne manque un keep-alive qui autrement serait nécessaire.


Je comprends la logique, mais cela ressemble à l'un des scénarios infinis de "comment augmenter les chances que les choses fonctionnent avec des wrappers / classes de base cassés / vous le nommez". Si tout implémente le modèle Dispose et supprime correctement la finalisation sur lui-même, il n'y a pas besoin de s'inquiéter à ce sujet, et, je crois, la question d'origine portait sur le modèle, pas sur la multitude d'extraits de code cassés. Juste au cas où: je n'ai pas voté contre, parce que votre contribution est toujours sur le sujet, à mon humble avis.



2
votes

Je n'ai pas encore vu cette utilisation particulière de Dispose mentionnée, alors j'ai pensé signaler une source courante de fuites de mémoire lorsque vous n'utilisez pas le modèle de disposition.

Visual Studio 2017 se plaint en fait de cela via l'analyse de code statique que je devrais "implémenter le modèle de disposition". Notez que j'utilise SonarQube et SolarLint, et je ne pense pas que Visual Studio le rattrapera seul. FxCop (un autre outil d'analyse de code statique) le fera probablement, bien que je ne l'ai pas testé.

Je note que le code ci-dessous pour montrer que le modèle de disposition est également là pour se protéger contre quelque chose comme celui-ci, qui n'a aucun ressources:

public sealed class UsesFoo : IDisposable
{
    public Foo MyFoo { get; }

    public UsesFoo(Foo foo)
    {
        MyFoo = foo;
    }

    public void Dispose()
    {
        MyFoo?.Dispose();
    }
}

public static class UsesFooFactory
{
    public static UsesFoo Create()
    {
        var bar = new Bar();
        bar.SomeEvent += Bar_SomeEvent;
        return new UsesFoo(bar);
    }

    private static void Bar_SomeEvent(object sender, EventArgs e)
    {
        //Do stuff
    }
}

Ce qui précède illustre un très mauvais code. Ne fais pas ça. Pourquoi ce code horrible? C'est à cause de ceci:

Foo foo = new Bar();
//Does NOT call Bar.Dispose()
foo.Dispose();

Supposons que ce code horrible a été exposé dans notre API publique. Considérez les classes ci-dessus utilisées par les consommateurs:

public class Foo : IDisposable
{
    IDisposable boo;

    public void Dispose()
    {
        boo?.Dispose();
    }
}

public class Bar : Foo
{
    //Memory leak possible here
    public event EventHandler SomeEvent;

    //Also bad code, but will compile
    public void Dispose()
    {
        someEvent = null;
        //Still bad code even with this line
        base.Dispose();
    }
}

Les consommateurs sont-ils parfaits? Non .... UsesFooFactory devrait probablement aussi se désinscrire de l'événement. Mais cela met en évidence un scénario courant où un abonné d'événement survit à l ' éditeur .

J'ai vu des événements provoquer d'innombrables fuites de mémoire. Surtout dans des bases de code très volumineuses ou extrêmement performantes.

Je peux également à peine compter combien de fois j'ai vu des objets vivre longtemps après leur élimination. C'est une façon très courante de nombreux profileurs de trouver des fuites de mémoire (objets supprimés toujours retenus par une racine GC quelconque).

Encore une fois, un exemple trop simplifié et un code horrible. Mais ce n'est vraiment jamais une bonne pratique d'appeler Dispose sur un objet et pas de s'attendre à ce qu'il supprime tout l'objet, qu'il ait été dérivé d'un million de fois ou non. p>

Modifier

Veuillez noter que cette réponse ne concerne intentionnellement que les ressources gérées, montrant que le modèle de suppression est également utile dans ce scénario. Cela n'aborde pas le cas d'utilisation des ressources non gérées, car je sentais qu'il y avait un manque de concentration sur les utilisations gérées uniquement. Et il y a beaucoup d'autres bonnes réponses ici qui en parlent.

Je noterai cependant quelques petites choses qui sont importantes lorsqu'il s'agit de ressources non gérées. Le code ci-dessus peut ne pas adresser les ressources non gérées, mais je tiens à préciser qu'il ne contredit pas la manière dont elles devraient être gérées.

Il est extrêmement important d'utiliser finaliseurs lorsque votre classe est responsable des ressources non gérées. En bref, les finaliseurs sont automatiquement appelés par le garbage collector. Cela vous donne donc une garantie raisonnable qu'il sera toujours appelé à un moment donné. Ce n'est pas à l'épreuve des balles, mais loin d'espérer que le code utilisateur appelle Dispose.

Cette garantie n'est pas vraie pour Dispose . Un objet peut être récupéré par le GC sans que Dispose ne soit jamais appelé. C'est la principale raison pour laquelle les finaliseurs sont utilisés pour les ressources non gérées. Le GC lui-même ne gère que les ressources gérées.

Mais je noterai également que les finaliseurs tout aussi importants ne doivent pas être utilisés pour nettoyer les ressources gérées. Il y a d'innombrables raisons pour lesquelles (c'est le travail du GC de faire cela après tout) mais l'un des plus gros inconvénients de l'utilisation des finaliseurs est de retarder le ramassage des ordures sur un objet.

Le GC, voir un objet est libre de récupérer mais a un finaliseur, retardera la collecte en plaçant l'objet dans la file d'attente du finaliseur. Cela ajoute une durée de vie inutile à un objet, plus une pression supplémentaire sur le GC.

Enfin, je noterai que les finaliseurs ne sont pas déterministes pour cette raison, malgré la syntaxe similaire à quelque chose comme un destructeur en C ++. Ce sont des bêtes très différentes. Vous ne devez jamais compter sur un finaliseur pour nettoyer les ressources non gérées à un moment donné.


20 commentaires

Sans conteste, c'est un très mauvais code. Pourriez-vous expliquer pourquoi on devrait écrire ~ Base () {} dans la classe de base, si la base ne possède pas elle-même de ressources non gérées? Parce que j'ai encore l'impression de mal comprendre vos commentaires.


Le problème avec le code ci-dessus est l'implémentation défectueuse de la classe dérivée. Maintenant, avoir le modèle de disposition dans la classe de base comment appliquerait-il pour implémenter correctement une classe dérivée? En d'autres termes, comment le modèle de disposition résoudrait-il le problème?


@quantificon Pour répondre à votre question, si la classe de base implémente correctement le modèle de disposition mais pas les classes dérivées, alors vous avez raison, c'est toujours du mauvais code. La chaîne entière doit implémenter le modèle. Cela dit, si la classe de base n'implémente pas correctement le modèle de disposition, toutes les classes dérivées ont des problèmes qu'elles ne peuvent pas résoudre seules.


Définir la liste d'appels d'un événement sur null est inutile. C'est à moins que vous ne prévoyiez d'éliminer l'objet tout en conservant des références enracinées à celui-ci pendant une période prolongée, ce que vous ne devriez absolument pas faire (et si vous le faisiez, ce serait le bogue, ne pas omettre l'annulation du champs classes). L'annulation des champs d'un objet qui est sur le point de sortir de toute façon est simplement redondant. Ils sont sur le point de ne plus être des références enracinées, donc ce n'est pas nécessaire.


De plus, le correctif du problème "Une classe dérivée n'appelle pas la méthode de disposition de la classe de base" est pour la classe dérivée d'appeler base.Dispose () , pas pour la classe de base à implémenter " le modèle de disposition ", car cela ne résout pas du tout le problème.


@ Sergey.quixoticaxis.Ivanov Nulle part dans la documentation indique-t-il que la classe de base doit avoir un finaliseur. Je ne voulais pas non plus insinuer cela.


@ Zer0 "Sinon, il est tout à fait plausible qu'il existe des ressources non gérées dans quelque chose qui dérive de votre classe. Dans ce cas, vous avez besoin du modèle de disposition (y compris les finaliseurs)" c'est votre devis.


@ Sergey.quixoticaxis.Ivanov Cela devrait probablement se lire "(y compris les finaliseurs si nécessaire)".


Si je vous comprends bien, votre déclaration concerne principalement la virtualisation de la chaîne complète des appels de nettoyage. Dans ce cas, quelle est la différence entre la virtualisation de Dispose et Dispose (suppression des booléens) et quel est l'intérêt de préférer le second au premier en dehors de l'unification avec le nettoyage des ressources non gérées.


@ Zer0 ah, ok) Désolé, j'ai passé beaucoup de temps à supprimer du code comme ~ A () {Dispose (false); } // juste au cas où , donc c'est un thème douloureux pour moi.


@Servy qui ne résout pas ce scénario Base foo = new Derived (); foo.Dispose (); . Le correctif correct est d'avoir une méthode de Dispose virtuelle. Ce qui fait partie du modèle d'élimination. Donc non, éditer simplement le code ci-dessus pour appeler base.Dispose () est toujours un mauvais code. Je vais éditer.


@ Zer0 Et comment est-ce mauvais? Le simple fait de dire «c'est mauvais» ne veut rien dire.


@Servy a mis à jour la réponse. En bref, lorsque j'appelle Dispose sur un objet, je m'attends à ce que cet objet soit éliminé. Je ne me soucie vraiment pas du type de variable auquel je fais référence en le faisant. C'est pourquoi des méthodes virtuelles existent. Et pourquoi le modèle de disposition les utilise.


Tout ce dont vous parlez est parfaitement valide, mais je ne trouve toujours pas dans votre réponse une corrélation avec un modèle de disposition qui ne concerne pas seulement la virtualisation de Dispose , mais également des signatures spécifiques et des ordres d'appels.


@quantificon Cette réponse ne couvre pas intentionnellement les ressources non gérées et les finaliseurs. Je voulais uniquement souligner que le modèle est utile même si vous n'utilisez pas d'objets non gérés. Ce qui inclut l'utilisation d'une méthode de disposition virtuelle . Certains peuvent trouver cela utile.


@ Zer0 Rendre la méthode virtuelle ne fait rien pour résoudre le problème. Vous devez toujours appeler la méthode de la classe de base, qu'elle soit virtuelle ou non. "Le modèle de disposition" ne fait rien pour résoudre ce problème. Le correctif consiste à appeler la méthode de classe de base quel que soit le style que vous utilisez . Et votre réponse est tout simplement fausse. Votre "mauvais" code fait en fait disposer des ressources de la classe de base. Les méthodes virtuelles existent de sorte que lorsqu'une méthode donnée est appelée sur une instance typée statiquement dans la classe de base , la classe dérivée peut contrôler complètement le comportement.


"Mais cela met en évidence un scénario courant où un abonné à un événement survit à l'éditeur. J'ai vu des événements provoquer d'innombrables fuites de mémoire." Mais ce n'est pas le cas. L'éditeur est hors de portée, il est donc nettoyé par le GC, de sorte qu'il ne prolonge pas la durée de vie de l'abonné, même s'il n'y a pas de code de suppression pour effacer l'événement.


"Mais ce n'est vraiment jamais une bonne pratique d'appeler Dispose sur un objet et de ne pas s'attendre à ce qu'il supprime tout l'objet" C'est vrai, mais le modèle de disposition n'affecte pas si c'est vrai ou non . C'est aux classes dérivées de dériver le type correct quelle que soit la façon dont la classe de base est implémentée , pour que le type fonctionne correctement. La classe de base ne peut pas forcer les classes dérivées à faire cela, ni les empêcher de fournir une extension boguée de la classe.


Zer0 Je comprends votre point. Si vous souhaitez prendre en charge la dérivation de votre classe, vous devez rendre la méthode Dispose virtuelle. Mais vous n’avez toujours pas besoin d’implémenter le modèle Dispose complet


@ Zer0, je ne sais pas pourquoi vous parlez de ressources finales et non gérées. Le modèle lui-même peut exister sans ces choses et cela est clairement indiqué dans l'article. J'ai cependant des doutes sur le raisonnement de reclassement de son application lorsqu'il existe des moyens plus simples d'obtenir le même résultat (le modèle peut également être appliqué, mais à quoi bon trop compliquer), c'est la raison principale pour laquelle j'ai soulevé la question. J'ai mis à jour ma réponse avec un exemple d'application du modèle de disposition uniquement lorsque cela est nécessaire.