Подтвердить что ты не робот

Слабая модель обработчика событий для использования с lambdas

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

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

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

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

4b9b3361

Ответ 1

"Ответ

(Подробнее см. ниже, если вы хотите увидеть, как я попал в это решение)

Использование, учитывая элемент управления с ванильным событием 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)

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

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

Реализация:

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

Деталь

Моя начальная точка была Egor отличный ответ (см. ссылку для версии с комментариями):

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 и т.д.

Итак, я попытался сделать его еще больше общим:

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 делегатом.

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

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

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

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

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

Запоздалая мысль

Я тщательно порекомендовал потратить время, чтобы узнать об реактивной структуре (или как бы то ни было, что она называется). Это ОЧЕНЬ интересно, и немного раздумья. Я подозреваю, что он также сделает такие вопросы как лишние.

До сих пор наиболее интересными вещами, которые я видел, были видео на Channel9.

Ответ 2

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

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

Так же просто!

Ответ 3

Я искал решение в течение долгого времени, и большинство из них используют неприятное отражение, но ответ Бенджола велик. Я изменил его, чтобы добавить поддержку для негенерированных EventHandler, DependencyPropertyChangedEventArgs, которые не наследуются от EventArgs и позволяют вручную самостоятельно регистрировать событие. Меня очень интересовали бы мысли людей, особенно Бенджоль.

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

Ответ 4

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

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