Question

J'ai une classe qui gère les événements d'un contrôle WinForms. En fonction de ce que l'utilisateur fait, je diffuse 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 facilement. Si possible, j'aimerais que cela soit fait de manière non exclusive, et il semble que ce soit un travail pour IDisposable. Cependant, la plupart des documents recommandent IDisposable uniquement lors de l’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 (), suis-je en train de pervertir son intention? Devrais-je plutôt fournir une fonction Unsubscribe () et l'appeler?

Modifier: voici un code factice qui montre ce que je fais (avec IDisposable). Mon implémentation actuelle est liée à une liaison de données propriétaire (long récit).

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 actuel, le " EventListener " la classe est plus impliquée et chaque instance est particulièrement significative. J'utilise ces éléments dans une collection et je les crée / les détruis lorsque l'utilisateur clique dessus.

Conclusion

J'accepte les réponses de gbjbaanb , du moins pour le moment. J’estime que l’avantage d’utiliser une interface familière l’emporte sur l’inconvénient possible de l’utiliser là où aucun code non géré n’est impliqué (comment un utilisateur de cet objet pourrait-il même le savoir?).

Si quelqu'un n'est pas d'accord, merci de poster / commenter / modifier. Si un meilleur argument peut être opposé à IDisposable, je modifierai la réponse acceptée.

Était-ce utile?

La solution

Oui, allez-y. Bien que certaines personnes pensent qu'IDisposable est implémenté uniquement pour les ressources non gérées, ce n'est pas le cas - les ressources non gérées s'avèrent être la plus grosse victoire et la raison la plus évidente de l'implémenter. Je pense que son idée a été retenue parce que les gens ne pouvaient penser à aucune autre raison de l’utiliser. Ce n'est pas comme un finaliseur, ce qui pose un problème de performances et n'est pas facile à gérer pour le GC.

Mettez n'importe quel code de rangement dans votre méthode de disposition. Ce sera plus clair, plus propre et beaucoup plus susceptible d’empêcher les fuites de mémoire et une fichue vue plus facile à utiliser correctement que d’essayer de ne pas oublier de supprimer vos références.

L'intention d'IDisposable est d'améliorer le fonctionnement de votre code sans que vous ayez à effectuer beaucoup de travail manuel. Utilisez son pouvoir en votre faveur et surmontez une "intention de conception" artificielle. non-sens.

Je me souviens qu'il était déjà assez difficile de persuader Microsoft de l'utilité de la finalisation déterministe lorsque .NET est apparu pour la première fois. Nous avons gagné la bataille et convaincu de l'ajouter (même s'il ne s'agissait que d'un modèle de conception à l'époque), utilisez-le!

Autres conseils

Mon vote personnel serait d’avoir une méthode de désinscription pour 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 aucune ressource non gérée et ne devez donc pas implémenter IDisposable.

IDisposable peut être utilisé pour gérer les abonnements aux événements, mais ne devrait probablement pas. Pour un exemple, je vous renvoie à WPF. C'est une bibliothèque qui regorge d'événements et de gestionnaires d'événements. Pourtant, pratiquement aucune classe dans WPF n'implémente IDisposable. Je considérerais cela comme une indication que les événements devraient être gérés d’une autre manière.

Un problème qui me dérange à propos de l'utilisation du modèle IDisposable pour la désinscription aux événements est le problème de la finalisation.

La fonction

Dispose () dans IDisposable est censée être appelée par le développeur, CEPENDANT, si elle n'est pas appelée par le développeur, il est entendu que GC appellera cette fonction (par le modèle standard IDisposable , au moins). Dans votre cas, cependant, si vous n'appelez pas Dispose , personne d'autre ne le fera - L'événement reste et la référence forte empêche le GC d'appeler le finaliseur.

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

Je pense que le jetable concerne tout ce que GC ne peut pas gérer automatiquement et les références d'événements comptent dans mon livre. Voici une classe d’aide que j’ai imaginée.

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 transmettre en tant que paramètre au constructeur d'assistance, mais cela ne semble pas avoir d'effet pervers.

D'après tout ce que j'ai lu sur les consommables, je dirais qu'ils ont été principalement inventés pour résoudre un problème: libérer les ressources système non gérées rapidement. Cependant, tous les exemples que j'ai trouvés ne sont pas seulement centrés sur le sujet des ressources non gérées, ils ont également une autre propriété en commun: Dispose est appelé simplement pour accélérer un processus qui, autrement, aurait eu lieu automatiquement plus tard (GC - > finaliseur - > disposer)

L'appel d'une méthode de suppression qui se désabonne d'un événement ne se produira toutefois jamais automatiquement, même si vous ajoutiez un finaliseur capable d'appeler votre disposition. (du moins pas tant que l'objet propriétaire de l'événement existe - et s'il était appelé, vous ne bénéficieriez pas de la désinscription, car l'objet propriétaire de l'événement disparaîtrait de toute façon)

La différence principale réside donc dans le fait que les événements créent en quelque sorte un graphe d'objet qui ne peut pas être collecté, car l'objet de gestion d'événement devient soudainement référencé par le service que vous vouliez simplement référencer / utiliser. Vous êtes soudainement contraint d'appeler Dispose - aucune élimination automatique n'est possible. Dispose aurait ainsi une signification subtile que celle trouvée dans tous les exemples où un appel Dispose - en théorie sale;) - n'est pas nécessaire, car il serait appelé automatiquement (à un moment donné) ...

Quoi qu'il en soit. Étant donné que le modèle jetable est déjà assez compliqué (traite avec des finaliseurs difficiles à obtenir et de nombreuses directives / contrats) et, ce qui est plus important dans la plupart des points, n’a rien à voir avec le sujet de référence du retour en arrière, je dirais que ce serait Il est plus facile d’obtenir cette distinction dans notre tête en n’utilisant tout simplement pas cette métaphore pour quelque chose que l’on pourrait appeler "unroot from object graph". / " arrêter " / "désactiver".

Ce que nous voulons réaliser est de désactiver / arrêter certains comportements (en vous désabonnant d'un événement). Il serait bien d’avoir une interface standard telle que IStoppable avec une méthode Stop (), qui, par contrat, est uniquement axée sur

  • obtenir l'objet (+ tous ses propres arrêts) déconnecté des événements de tout objet qu'il n'a pas créé par lui-même
  • pour qu'il ne soit plus appelé de manière implicite (par conséquent, il peut être perçu comme étant arrêté)
  • peut être collecté dès que toutes les références traditionnelles sur cet objet ont disparu

Appelons la seule méthode d'interface qui effectue la désinscription "Stop ()". Vous sauriez que l'objet arrêté est dans un état acceptable mais que seul il est arrêté. Peut-être une simple propriété " Arrêté " serait également un plaisir d'avoir.

Il serait même logique d’avoir une interface "IRestartable". qui hérite de IStoppable et a en plus une méthode "Restart ()" si vous souhaitez simplement mettre en pause un comportement dont vous aurez certainement besoin à l'avenir, ou stocker un objet de modèle supprimé dans un historique pour une récupération ultérieure.

Après tout avoir écrit, je dois avouer que je viens de voir un exemple d’IDisposable quelque part ici: http://msdn.microsoft.com/en-us/library/dd783449%28v=VS.100%29.aspx Mais de toute façon, jusqu’à ce que tous les détails et la motivation initiale de IObservable soient connus, je dirais que ce n’est pas le meilleur exemple de cas d’utilisation

  • puisque c’est à nouveau un système assez compliqué et que nous n’avons qu’un petit problème ici
  • et il se peut que l'une des motivations de ce tout nouveau système soit de se débarrasser des événements en premier lieu, ce qui entraînerait une sorte de débordement de pile concernant la question initiale

Mais il semble qu'ils soient sur la bonne voie. Quoi qu’il en soit: ils auraient dû utiliser mon interface " IStoppable " ;) car je crois fermement qu'il y a une différence de

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

Je vote aussi pour une méthode de désinscription sur votre propre interface.

Une option peut être de ne pas vous désabonner du tout, mais simplement de changer le sens de l'abonnement. Si le gestionnaire d'événements peut être rendu suffisamment intelligent pour savoir ce qu'il est censé faire en fonction du contexte, vous n'avez pas besoin de vous désabonner en premier lieu.

Cela peut ou peut ne pas être une bonne idée dans votre cas particulier - je ne pense pas que nous ayons vraiment assez d'informations - mais cela vaut la peine d'être examiné.

Une autre option consisterait à utiliser délégués faibles ou quelque chose comme les événements faibles de WPF, au lieu de devoir se désabonner explicitement .

P.S. [OT] Je considère que la décision de ne fournir aux délégués puissants que l'erreur de conception la plus chère de la plate-forme .NET.

Non, vous n'empêchez pas l'intention d'IDisposable. IDisposable est conçu comme un moyen polyvalent de garantir que lorsque vous avez fini d'utiliser un objet, vous pouvez nettoyer de manière proactive tout ce qui est lié à cet objet. Il ne doit pas s'agir uniquement de ressources non gérées, il peut également inclure des 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 allez implémenter IDisposable sur votre type, simplement pour pouvoir appeler Dispose () sur un autre objet géré. Ce n'est pas une perversion non plus, c'est juste une gestion ordonnée des ressources!

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