Слабая модель обработчика событий для использования с лямбдами - PullRequest
37 голосов
/ 17 ноября 2009

ОК, так что это скорее ответ, чем вопрос, но после того, как он задает этот вопрос и собирает различные биты из Дастин Кэмпбелл , Егор , а также последний совет из ' IObservable / Rx / Reactive framework ', я думаю, что я нашел работоспособное решение для этой конкретной проблемы. Он может быть полностью заменен платформой IObservable / Rx / Reactive, но только опыт покажет это.

Я специально создал новый вопрос, чтобы дать мне возможность объяснить, как я дошел до этого решения, поскольку оно может быть не сразу очевидным.

Есть много связанных вопросов, большинство из которых говорят вам, что вы не можете использовать встроенные лямбды, если хотите иметь возможность отсоединить их позже:

И это правда, что если ВЫ хотите иметь возможность отсоединить их позже, вам необходимо сохранить ссылку на свою лямбду. Однако, если вы просто хотите, чтобы обработчик событий отключался, когда ваш подписчик выпадает из области видимости, этот ответ для вас.

Ответы [ 4 ]

33 голосов
/ 17 ноября 2009

'The' ответ

(Подробнее читайте ниже, если хотите узнать, как я дошел до этого решения)

Использование, если задан элемент управления с событием vanilla MouseDown и конкретным событием EventHandler<ValueEventArgs> ValueEvent:

// 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

(* Это обходной путь от Rx )

(** важно избегать прямого вызова объекта подписчика здесь (например, помещая subscriber.DoSomething (e) или прямого вызова DoSomething (e), если мы находимся внутри класса Subscriber. При этом эффективно создается ссылка абонент, который полностью побеждает объект ...)

Примечание : в некоторых случаях это МОЖЕТ оставлять в памяти ссылки на классы обертывания, созданные для лямбд, но они весят только байты, так что я не слишком обеспокоен.

Реализация:

//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);
}

Деталь

Моя отправная точка была Отличный ответ Егора (см. Ссылку для версии с комментариями):

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;
}

Меня беспокоило то, что событие жестко закодировано в методе. Таким образом, это означает, что для каждого нового события есть новый метод для записи.

Я возился и смог найти это общее решение:

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);
}

Однако проблема этого решения в том, что оно ТОЛЬКО универсально, оно не может обрабатывать стандартные winforms MouseUp, MouseDown и т. Д ...

Итак, я попытался сделать это даже больше generic:

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);
}

Однако, как я намекнул здесь , это не скомпилируется, потому что нет способа ограничить T быть делегатом.

В этот момент я почти сдался. Нет смысла пытаться бороться со спецификациями C #.

Однако вчера я обнаружил метод Observable.FromEvent из среды Reactive, у меня не было реализации, но использование показалось немного знакомым и очень интересным:

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

Это был первый аргумент, который привлек мое внимание. Это обходной путь для отсутствия ограничения типа делегата. Мы берем это, передавая функцию, которая создаст делегата.

Объединение всего этого дает нам решение, показанное вверху этого ответа.

1059 * Запоздалая мысль * Я настоятельно рекомендовал уделить время, чтобы узнать о реактивной структуре (или как она там называется). Это очень интересно и немного ошеломляет. Я подозреваю, что это также сделает такие вопросы совершенно ненужными. Пока что самое интересное, что я видел, это видео на Channel9 .

5 голосов
/ 28 октября 2010

Если вы перейдете к CodePlex, есть проект под названием Sharp Observation , в котором автор создал хорошего слабого поставщика делегатов, реализованного в MSIL. Быстрый, гибкий, простой в использовании: например,

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

Так просто!

3 голосов
/ 01 июля 2011

Я долго искал решение, и большинство из них используют отвратительные размышления, но ответ Бенджола великолепен. Я настроил его, чтобы добавить поддержку неуниверсального EventHandler, DependencyPropertyChangedEventArgs, который не наследуется от EventArgs, и позволить вам вручную отменить регистрацию события самостоятельно. Я был бы очень заинтересован в мыслях людей, особенно 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;
        }
    }
}
2 голосов
/ 07 мая 2010

Подход Дастина Кэмпбелла уже превосходен. Единственное, что осталось - сохранить решение, интегрированное в .NET, - это действительно простой способ создания действительно общих обработчиков слабых событий:

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

...