39 votes

Modèle de gestionnaire d'événements faible à utiliser avec les lambdas

OK, donc c'est plus d'une réponse, qu'une question, mais après avoir posé cette question, et de rassembler les différents bits de Dustin Campbell, Egor, et aussi un dernier conseil à partir de laIObservable/Rx/Réactif cadre", je crois que j'ai une solution pour ce problème particulier. Il peut être totalement remplacées par IObservable/Rx/Réactif cadre, mais seule l'expérience peut montrer que.

J'ai délibérément créé une nouvelle question, à me donner de l'espace pour expliquer comment j'en suis arrivé à cette solution, car elle peut ne pas être immédiatement évident.

Il y a beaucoup de questions connexes, la plupart de vous dire que vous ne pouvez pas utiliser inline lambdas si vous voulez être en mesure de les détacher plus tard:

Et il est vrai que si VOUS voulez être en mesure de les détacher plus tard, vous avez besoin de garder une référence à votre lambda. Cependant, si vous voulez que le gestionnaire d'événement de la détacher de lui-même lors de votre abonné est hors de portée, cette réponse est pour vous.

35voto

Benjol Points 16334

"La" réponse

(Lire plus ci-dessous si vous voulez voir comment j'en suis arrivé à cette solution)

L'utilisation, compte tenu d'un contrôle de vanille MouseDown événement, et une EventHandler<ValueEventArgs> ValueEvent événement:

// for 'vanilla' events
SetAnyHandler<Subscriber, MouseEventHandler, MouseEventArgs>(
    h => (o,e) => h(o,e), //don't ask me, but it works*.
    h => control.MouseDown += h,
    h => control.MouseDown -= h,
    subscriber,
    (s, e) => s.DoSomething(e));  //**See note below

// for generic events
SetAnyHandler<Subscriber, ValueEventArgs>(
    h => control.ValueEvent += h,
    h => control.ValueEvent -= h,
    subscriber,
    (s, e) => s.DoSomething(e));  //**See note below

(*C'est une solution de contournement de Rx)

(** il est important d'éviter d'invoquer l'objet de l'abonné directement ici (par exemple mettre de l'abonné.DoSomething(e), ou en invoquant DoSomething(e) directement si nous sommes à l'intérieur de l'Abonné de la classe. En faisant cela crée effectivement une référence à l'abonné, ce qui va à l'encontre de l'objet...)

Remarque: dans certaines circonstances, cela PEUT laisser les références à l'emballage des classes créées pour les lambdas dans la mémoire, mais ils ne pèsent octets, donc je ne suis pas trop gêné.

Mise en œuvre:

//This overload handles any type of EventHandler
public static void SetAnyHandler<S, TDelegate, TArgs>(
    Func<EventHandler<TArgs>, TDelegate> converter, 
    Action<TDelegate> add, Action<TDelegate> remove,
    S subscriber, Action<S, TArgs> action)
    where TArgs : EventArgs
    where TDelegate : class
    where S : class
{
    var subs_weak_ref = new WeakReference(subscriber);
    TDelegate handler = null;
    handler = converter(new EventHandler<TArgs>(
        (s, e) =>
        {
            var subs_strong_ref = subs_weak_ref.Target as S;
            if(subs_strong_ref != null)
            {
                action(subs_strong_ref, e);
            }
            else
            {
                remove(handler);
                handler = null;
            }
        }));
    add(handler);
}

// this overload is simplified for generic EventHandlers
public static void SetAnyHandler<S, TArgs>(
    Action<EventHandler<TArgs>> add, Action<EventHandler<TArgs>> remove,
    S subscriber, Action<S, TArgs> action)
    where TArgs : EventArgs
    where S : class
{
    SetAnyHandler<S, EventHandler<TArgs>, TArgs>(
        h => h, add, remove, subscriber, action);
}

Le détail

Mon point de départ était Egor's excellente réponse (voir le lien pour la version avec commentaires):

public static void Link(Publisher publisher, Control subscriber) {
    var subscriber_weak_ref = new WeakReference(subscriber);
    EventHandler<ValueEventArgs<bool>> handler = null;
    handler = delegate(object sender, ValueEventArgs<bool> e) {
            var subscriber_strong_ref = subscriber_weak_ref.Target as Control;
            if (subscriber_strong_ref != null) subscriber_strong_ref.Enabled = e.Value;
            else {
                    ((Publisher)sender).EnabledChanged -= handler;
                    handler = null; 
            }
    };

    publisher.EnabledChanged += handler;
}

Ce qui m'a dérangé est que l'événement est codé en dur dans la méthode. Ce qui signifie que pour chaque nouvel événement, il y a une nouvelle méthode pour écrire.

J'ai trafiqué autour et réussi à venir avec cette solution générique:

private static void SetAnyGenericHandler<S, T>(
     Action<EventHandler<T>> add,     //to add event listener to publisher
     Action<EventHandler<T>> remove,  //to remove event listener from publisher
     S subscriber,                    //ref to subscriber (to pass to action)
     Action<S, T> action)             //called when event is raised
    where T : EventArgs
    where S : class
{
    var subscriber_weak_ref = new WeakReference(subscriber);
    EventHandler<T> handler = null;
    handler = delegate(object sender, T e)
    {
        var subscriber_strong_ref = subscriber_weak_ref.Target as S;
        if(subscriber_strong_ref != null)
        {
            Console.WriteLine("New event received by subscriber");
            action(subscriber_strong_ref, e);
        }
        else
        {
            remove(handler);
            handler = null;
        }
    };
    add(handler);
}

Cependant, le problème avec cette solution est qu'elle est générique, il ne peut pas gérer le standard winforms MouseUp, MouseDown, etc...

J'ai donc essayé de le rendre encore plus générique:

private static void SetAnyHandler<T, R>(
    Action<T> add,      //to add event listener to publisher
    Action<T> remove,   //to remove event listener from publisher
    Subscriber subscriber,  //ref to subscriber (to pass to action)
    Action<Subscriber, R> action) 
    where T : class
{
    var subscriber_weak_ref = new WeakReference(subscriber);
    T handler = null;
    handler = delegate(object sender, R e) //<-compiler doesn't like this line
    {
        var subscriber_strong_ref = subscriber_weak_ref.Target as Subscriber;
        if(subscriber_strong_ref != null)
        {
            action(subscriber_strong_ref, e);
        }
        else
        {
            remove(handler);
            handler = null;
        }
    };
    remove(handler);
}

Cependant, comme je l'ai déjà évoqué ici, ce ne compile pas, car il n'existe aucun moyen de restreindre T à un délégué.

À ce moment, j'ai à peu près abandonné. Il n'y a pas de point d'essayer de se battre avec le C# specs.

Cependant, hier, j'ai découvert l'Observable.FromEvent méthode de la Réactif cadre, je n'avais pas la mise en œuvre, mais l'usage semble un peu familier, et très intéressant:

var mousedown = Observable.FromEvent<MouseEventHandler, MouseDownEventArgs>(
      h => new MouseEventHandler(h),
      h => control.MouseDown += h,
      h => control.MouseDown -= h);

Il a été le premier argument qui a attiré mon attention. C'est la solution de contournement pour l'absence d'un délégué type de contrainte. Nous prenons en passant dans la fonction qui va créer le délégué.

Mettant tout cela ensemble nous donne la solution indiquée en haut de cette réponse.

Après coup

J'ai tout à fait recommandé de prendre le temps d'apprendre le réactif de cadre (ou de ce qu'il finit par être appelé). Il est TRÈS intéressant, et un peu mindblowing. Je soupçonne que ce sera aussi rendu à ce genre de questions totalement superflus.

Jusqu'à présent, la plupart des choses intéressantes que j'ai vu les vidéos sur Channel 9.

5voto

Mark Points 5924

Si vous vous dirigez vers CodePlex, il y a un projet appelé Sharp Observation dans lequel l'auteur a créé un bon fournisseur de délégués faible, implémenté dans MSIL. Rapide, flexible, facile à utiliser: par exemple

 Action<int,int> myDelegate = new Action<int,int>( aMethodOnMyClass );
myDelegate.MakeWeak();
 

Aussi simple que ça!

3voto

gfsdfsdf Points 11

Cela fait longtemps que je recherche une solution et la plupart des personnes utilisent une réflexion désagréable, mais la réponse de Benjohl est excellente. Je l'ai modifié pour ajouter la prise en charge de DependencyPropertyChangedEventArgs non générique EventHandler qui n'hérite pas de EventArgs et pour vous permettre de désenregistrer manuellement un événement vous-même. Je serais très intéressé par les pensées des gens, en particulier Benjohl.

 /// <summary>
/// Weakly registers for events using <see cref="WeakReference"/>.
/// </summary>
public sealed class WeakEvent
{
    private Action removeEventHandler;

    /// <summary>
    /// Initializes a new instance of the <see cref="WeakEvent"/> class.
    /// </summary>
    /// <param name="removeEventHandler">The remove event handler function.</param>
    private WeakEvent(Action removeEventHandler)
    {
        this.removeEventHandler = removeEventHandler;
    }

    /// <summary>
    /// Weakly registers the specified subscriber to the the given event of type 
    /// <see cref="EventHandler"/>.
    /// </summary>
    /// <example>
    /// Application application;
    /// WeakEvent.Register{TextBox, TextChangedEventArgs>(
    ///     this,
    ///     eventHandler => textBox.TextChanged += eventHandler,
    ///     eventHandler => textBox.TextChanged -= eventHandler,
    ///     (sender, e) => this.OnTextChanged(sender, e));
    /// </example>
    /// <typeparam name="S">The type of the subscriber.</typeparam>
    /// <param name="subscriber">The subscriber.</param>
    /// <param name="addEventhandler">The add eventhandler.</param>
    /// <param name="removeEventHandler">The remove event handler function.</param>
    /// <param name="action">The event execution function.</param>
    public static WeakEvent Register<S>(
        S subscriber,
        Action<EventHandler> addEventhandler,
        Action<EventHandler> removeEventHandler,
        Action<S, EventArgs> action)
        where S : class
    {
        return Register<S, EventHandler, EventArgs>(
            subscriber,
            eventHandler => (sender, e) => eventHandler(sender, e),
            addEventhandler,
            removeEventHandler,
            action);
    }

    /// <summary>
    /// Weakly registers the specified subscriber to the the given event of type 
    /// <see cref="EventHandler{T}"/>.
    /// </summary>
    /// <example>
    /// Application application;
    /// WeakEvent.Register{TextBox, TextChangedEventArgs>(
    ///     this,
    ///     eventHandler => textBox.TextChanged += eventHandler,
    ///     eventHandler => textBox.TextChanged -= eventHandler,
    ///     (sender, e) => this.OnTextChanged(sender, e));
    /// </example>
    /// <typeparam name="S">The type of the subscriber.</typeparam>
    /// <typeparam name="TEventArgs">The type of the event arguments.</typeparam>
    /// <param name="subscriber">The subscriber.</param>
    /// <param name="addEventhandler">The add eventhandler.</param>
    /// <param name="removeEventHandler">The remove event handler function.</param>
    /// <param name="action">The event execution function.</param>
    public static WeakEvent Register<S, TEventArgs>(
        S subscriber, 
        Action<EventHandler<TEventArgs>> addEventhandler, 
        Action<EventHandler<TEventArgs>> removeEventHandler,
        Action<S, TEventArgs> action)
        where S : class
        where TEventArgs : EventArgs
    {
        return Register<S, EventHandler<TEventArgs>, TEventArgs>(
            subscriber,
            eventHandler => eventHandler, 
            addEventhandler, 
            removeEventHandler, 
            action);
    }

    /// <summary>
    /// Weakly registers the specified subscriber to the the given event.
    /// </summary>
    /// <example>
    /// TextBox textbox;
    /// WeakEvent.Register{TextBox, TextChangedEventHandler, TextChangedEventArgs>(
    ///     this,
    ///     eventHandler => (sender, e) => eventHandler(sender, e),
    ///     eventHandler => textBox.TextChanged += eventHandler,
    ///     eventHandler => textBox.TextChanged -= eventHandler,
    ///     (sender, e) => this.OnTextChanged(sender, e));
    /// </example>
    /// <typeparam name="S">The type of the subscriber.</typeparam>
    /// <typeparam name="TEventHandler">The type of the event handler.</typeparam>
    /// <typeparam name="TEventArgs">The type of the event arguments.</typeparam>
    /// <param name="subscriber">The subscriber.</param>
    /// <param name="getEventHandler">The get event handler function.</param>
    /// <param name="addEventHandler">The add event handler function.</param>
    /// <param name="removeEventHandler">The remove event handler function.</param>
    /// <param name="action">The event execution function.</param>
    public static WeakEvent Register<S, TEventHandler, TEventArgs>(
        S subscriber, 
        Func<EventHandler<TEventArgs>, TEventHandler> getEventHandler,
        Action<TEventHandler> addEventHandler, 
        Action<TEventHandler> removeEventHandler,
        Action<S, TEventArgs> action)
        where S : class
        where TEventHandler : class
        where TEventArgs : EventArgs

    {
        WeakReference weakReference = new WeakReference(subscriber);

        TEventHandler eventHandler = null;
        eventHandler = getEventHandler(
            new EventHandler<TEventArgs>(
                (sender, e) =>
                {
                    S subscriberStrongRef = weakReference.Target as S;

                    if (subscriberStrongRef != null)
                    {
                        action(subscriberStrongRef, e);
                    }
                    else
                    {
                        removeEventHandler(eventHandler);
                        eventHandler = null;
                    }
                }));

        addEventHandler(eventHandler);

        return new WeakEvent(() => removeEventHandler(eventHandler));
    }

    public static WeakEvent Register<S>(
        S subscriber,
        Action<DependencyPropertyChangedEventHandler> addEventHandler,
        Action<DependencyPropertyChangedEventHandler> removeEventHandler,
        Action<S, DependencyPropertyChangedEventArgs> action)
        where S : class
    {
        WeakReference weakReference = new WeakReference(subscriber);

        DependencyPropertyChangedEventHandler eventHandler = null;
        eventHandler = new DependencyPropertyChangedEventHandler(
            (sender, e) =>
            {
                S subscriberStrongRef = weakReference.Target as S;

                if (subscriberStrongRef != null)
                {
                    action(subscriberStrongRef, e);
                }
                else
                {
                    removeEventHandler(eventHandler);
                    eventHandler = null;
                }
            });

        addEventHandler(eventHandler);

        return new WeakEvent(() => removeEventHandler(eventHandler));
    }

    /// <summary>
    /// Manually unregisters this instance from the event.
    /// </summary>
    public void Unregister()
    {
        if (this.removeEventHandler != null)
        {
            this.removeEventHandler();
            this.removeEventHandler = null;
        }
    }
}
 

2voto

Stefan Dragnev Points 3466

L'approche de Dustin Campbell est déjà excellente. Enregistrer une solution intégrée à .NET est un moyen très simple de créer des gestionnaires d’événements faibles génériques:

http://puremsil.wordpress.com/2010/05/03/generic-weak-event-handlers/

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