Utiliser IDisposable pour désabonner les événements

j'ai une classe qui gère les événements à partir d'un contrôle WinForms. Basé sur ce que l'utilisateur fait, je défère une instance de la classe et en crée une nouvelle pour gérer le même événement. Je dois d'abord désinscrire l'ancienne instance de l'événement - assez facile. J'aimerais le faire de manière non-exclusive si possible, et il semble que ce soit un travail pour IDisposable. Cependant, la plupart des documents ne recommandent IDisposable qu'en cas d'utilisation de ressources non gérées, ce qui ne s'applique pas ici.

si J'implémente IDisposable et que je me désabonne de L'événement dans Dispose(), Est-ce que je perverse son intention? Devrais-je plutôt fournir une fonction de désabonnement() et l'appeler?


Edit: voici un code fictif qui montre en quelque sorte ce que je fais (en utilisant IDisposable). Ma mise en œuvre actuelle est liée à certaines données propriétaires liant (longue histoire).

class EventListener : IDisposable
{
    private TextBox m_textBox;

    public EventListener(TextBox textBox)
    {
        m_textBox = textBox;
        textBox.TextChanged += new EventHandler(textBox_TextChanged);
    }

    void textBox_TextChanged(object sender, EventArgs e)
    {
        // do something
    }

    public void Dispose()
    {
        m_textBox.TextChanged -= new EventHandler(textBox_TextChanged);
    }
}

class MyClass
{
    EventListener m_eventListener = null;
    TextBox m_textBox = new TextBox();

    void SetEventListener()
    {
        if (m_eventListener != null) m_eventListener.Dispose();
        m_eventListener = new EventListener(m_textBox);
    }
}

dans le code réel, la classe" EventListener " est plus impliquée, et chaque instance est significative. Je les utilise dans une collection, et les créer/les détruire comme l'utilisateur clique autour.


Conclusion

j'accepte réponse de gbjbaanb , du moins pour l'instant. Je pense que l'avantage d'utiliser une interface familière l'emporte sur tout inconvénient possible de l'utiliser là où aucun code non géré (comment un utilisateur de cet objet même de savoir que?).

Si quelqu'un n'est pas d'accord - s'il vous plaît post/commentaire/modifier. Si un meilleur argument peut être fait contre IDisposable, alors je vais changer la réponse acceptée.

45
demandé sur Community 2009-01-17 01:26:22

9 réponses

oui, vas-y. Bien que certaines personnes pensent que IDisposable est mis en œuvre seulement pour les ressources non gérées, ce n'est pas le cas - les ressources non gérées se trouvent être la plus grande victoire, et la raison la plus évidente pour la mettre en œuvre. Je pense qu'il a acquis cette idée parce que les gens ne pouvaient pas penser à une autre raison de l'utiliser. Ce n'est pas comme un finaliseur qui est un problème de performance et qui n'est pas facile à gérer pour le GC.

mettez n'importe quel code de rangement dans votre méthode d'élimination. Ce sera plus clair, plus propre et beaucoup plus susceptible d'empêcher les fuites de mémoire et une vue plus facile à utiliser correctement que d'essayer de se rappeler de ne pas faire vos références.

L'intention D'IDisposable est d'améliorer le fonctionnement de votre code sans que vous ayez à faire beaucoup de travail manuel. Utilisez son pouvoir en votre faveur et obtenez sur certains artificiel "intention de conception" non-sens.

je me souviens qu'il était assez difficile de convaincre Microsoft de l'utilité de finalisation déterministe lorsque .NET est sorti pour la première fois - nous avons gagné la bataille et les avons persuadés de l'Ajouter (même si ce n'était qu'un dessin à l'époque), utilisez-le!

38
répondu gbjbaanb 2009-01-16 22:43:51

mon vote personnel serait d'avoir une méthode de désabonnement afin de supprimer la classe des événements. IDisposable est un modèle destiné à la libération déterministe de ressources non gérées. Dans ce cas, vous ne gérez pas de ressources non gérées et ne devriez donc pas mettre en œuvre IDisposable.

IDisposable peut être utilisé pour gérer les abonnements à des événements, mais ne devrait probablement pas. Par exemple, je vous renvoie à la WPF. C'est une bibliothèque pleine d'événements et d'événements manipulateur. Pourtant, pratiquement aucune classe de la WPF ne met en œuvre IDisposable. Je prendrais cela comme une indication que les événements doivent être gérés d'une autre manière.

12
répondu JaredPar 2009-01-16 22:34:12

une chose qui me dérange à propos de l'utilisation du modèle IDisposable pour se désabonner des événements est la question de finalisation.

Dispose() fonction dans IDisposable est censé être appelé par le développeur, cependant, s'il n'est pas appelé par le développeur, il est entendu que le GC appellera cette fonction (par le modèle standard IDisposable , au moins). Dans votre cas, cependant, si vous ne l'appelez pas Dispose personne d'autre ne Le reste et la forte la référence retient GC d'appeler le finaliseur.

le simple fait que Dispose () ne sera pas appelé automatiquement par GC me semble suffisant pour ne pas utiliser IDisposable dans ce cas. Peut-être que cela nécessite une nouvelle interface spécifique à l'application qui dit que ce type d'objet doit avoir une fonction Nettoyage appelé à être éliminé par GC.

8
répondu VitalyB 2017-02-06 13:13:08

je pense que jetable est pour tout ce que GC ne peut pas prendre en charge automatiquement, et les références d'événement comptent dans mon livre. Voici un cours d'aide que j'ai inventé.

public class DisposableEvent<T> : IDisposable
    {

        EventHandler<EventArgs<T>> Target { get; set; }
        public T Args { get; set; }
        bool fired = false;

        public DisposableEvent(EventHandler<EventArgs<T>> target)
        {
            Target = target;
            Target += new EventHandler<EventArgs<T>>(subscriber);
        }

        public bool Wait(int howLongSeconds)
        {
            DateTime start = DateTime.Now;
            while (!fired && (DateTime.Now - start).TotalSeconds < howLongSeconds)
            {
                Thread.Sleep(100);
            }
            return fired;
        }

        void subscriber(object sender, EventArgs<T> e)
        {
            Args = e.Value;
            fired = true;
        }

        public void Dispose()
        {
            Target -= subscriber;
            Target = null;
        }

    }

qui vous permet d'écrire ce code:

Class1 class1 = new Class1();
            using (var x = new DisposableEvent<object>(class1.Test))
            {
                if (x.Wait(30))
                {
                    var result = x.Args;
                }
            }

un effet secondaire, vous ne devez pas utiliser le mot-clé event sur vos événements, car cela empêche de les passer comme paramètre au constructeur helper, cependant, cela ne semble pas avoir d'effets néfastes.

5
répondu Jason Coyne 2009-08-05 15:16:04

de tout ce que j'ai lu au sujet des jetables, je dirais qu'ils ont été inventés principalement pour résoudre un problème: libérer les ressources du système non géré de manière opportune. Mais tout de même tous les exemples que j'ai trouvé ne sont pas seulement axés sur le sujet des ressources non gérées, mais ont également une autre propriété en commun: dispose est appelé juste pour accélérer un processus qui, autrement, aurait eu lieu plus tard automatiquement (GC -> finaliseur -> Eliminer)

appelant une méthode d'élimination qui se désabonne d'un événement cependant ne se produirait jamais automatiquement, même si vous ajouteriez un finaliseur qui appellerait votre élimination. (du moins, pas tant que l'objet possesseur d'événements existe - et s'il était appelé, vous ne bénéficieriez pas de la désinscription, puisque l'objet possesseur d'événements serait également parti de toute façon)

donc la principale différence est que les événements construisent d'une manière ou d'une autre un graphe objet qui ne peut pas être collected, puisque l'objet de traitement d'événement devient soudainement référencé du service que vous vouliez juste référencer/utiliser. Vous êtes soudainement forcé d'appeler Dispose-no automatique l'élimination est possible. Dispose obtiendrait ainsi un sens subtil autre que celui trouvé dans tous les exemples où un appel Dispose - dans la théorie sale ;) - n'est pas nécessaire, car il serait appelé automatiquement (à un moment donné)...

en tout cas. Depuis le modèle jetable est quelque chose qui est déjà assez compliqué (traite avec des finalisateurs qui sont difficiles à obtenir droite et de nombreuses lignes directrices / contrats) et plus important dans la plupart des points n'a rien à voir avec l'événement retour sujet de référence, je dirais qu'il serait plus facile d'obtenir que séparé dans nos têtes en n'utilisant tout simplement pas cette métaphore pour quelque chose qui pourrait être appelé "unroot de l'objet graphique" / "stop" / "turn off".

ce que nous voulons réaliser, c'est désactiver / arrêter certains comportements (en se désabonnant d'un événement). Il serait bien d'avoir une interface standard comme IStoppable avec une méthode Stop (), qui par contrat est juste centré sur

  • obtenir l'objet (+ tous ses propres arrêts) déconnecté des événements de tout objet qu'il n'a pas créé par ses propres
  • de sorte qu'il ne sera plus appelé de manière implicite de style événement plus (donc peut être perçu comme arrêté)
  • peut être collecté dès que les références traditionnelles sur cet objet ont disparu

appelons la seule méthode d'interface qui fait le désabonnement" Stop ()". Vous savez que l'objet arrêté est dans un état acceptable mais seulement arrêté. Peut-être qu'une simple propriété "arrêtée" serait aussi une bonne chose à avoir.

il serait même logique d'avoir une interface "IRestartable" qui hérite de IStoppable et a en outre un méthode" Restart () " si vous voulez juste mettre en pause un certain comportement qui sera certainement nécessaire à nouveau dans le futur, ou pour stocker un objet model supprimé dans un historique pour une récupération ultérieure.

après tout ce que j'ai écrit, je dois avouer que je viens de voir un exemple D'IDisposable quelque part par ici: http://msdn.microsoft.com/en-us/library/dd783449%28v=VS.100%29.aspx Mais en tout cas jusqu'à ce que j'obtienne tous les détails et la motivation originale de IObservable i serait dire qu'il n'est pas l'exemple de cas de la meilleure utilisation

  • depuis, c'est un système assez compliqué autour de lui et nous n'avons qu'un petit problème ici
  • et il se pourrait que l'une des motivations de ce nouveau système soit de se débarrasser des événements en premier lieu, ce qui aboutirait à une sorte de débordement de la pile concernant la question initiale

mais il semble qu'ils soient sur une certaine droite piste. Quoi qu'il en soit: ils auraient dû utiliser mon interface "IStoppable";) car je crois fermement qu'il y a une différence dans

  • disposer:" vous devrait appeler cette méthode ou quelque chose pourrait fuite si le CG arrive trop tard"....

et

  • Stop: "vous appeler cette méthode pour arrêter un certain comportement
4
répondu Sebastian Gregor 2011-01-05 01:36:40

IDisposable est fermement sur les ressources, et la source de suffisamment de problèmes pour ne pas brouiller les eaux plus loin je pense.

je vote pour une méthode de désabonnement sur votre propre Interface aussi.

3
répondu annakata 2009-01-16 22:39:03

une option peut être de ne pas se désabonner du tout - juste pour changer ce que signifie l'abonnement. Si le gestionnaire d'événements peut être fait assez intelligent pour savoir ce qu'il doit faire en fonction du contexte, vous n'avez pas besoin de vous désinscrire en premier lieu.

ce n'est peut - être pas une bonne idée dans votre cas particulier - Je ne pense pas que nous ayons assez d'informations-mais cela vaut la peine d'y réfléchir.

3
répondu Jon Skeet 2009-01-16 23:20:32

une autre option serait d'utiliser délégués faibles ou quelque chose comme wpfs événements faibles , au lieu d'avoir à se désabonner explicitement.

P. [OT] je considère que la décision de fournir seulement des délégués forts l'erreur de conception la plus chère de la plate-forme .NET.

3
répondu Andreas Huber 2009-01-17 08:49:33

Non, vous n'empêchez pas L'intention D'IDisposable. IDisposable est conçu comme un moyen universel de s'assurer que lorsque vous avez terminé l'utilisation d'un objet, vous pouvez nettoyer proactivement tout ce qui est lié à cet objet. Il ne doit pas s'agir uniquement de ressources non gérées, il peut aussi s'agir de ressources gérées. Et un abonnement à un événement n'est qu'une autre ressource gérée!

un scénario similaire qui se produit fréquemment dans la pratique est que vous mettrez en œuvre IDisposable votre type, purement pour vous assurer que vous pouvez appeler Dispose() sur un autre objet géré. Ce n'est pas une perversion non plus, c'est juste une bonne gestion des ressources!

1
répondu Tim Lovell-Smith 2018-07-10 10:45:29