簡體   English   中英

與lambdas一起使用的弱事件處理程序模型

[英]Weak event handler model for use with lambdas

好吧,所以這更像是一個答案而不是一個問題,但在提出這個問題之后 ,將Dustin CampbellEgor的各個部分以及“ IObservable / Rx / Reactive框架 ”的最后一個提示拉到一起,我想我為這個特殊問題制定了可行的解決方案。 它可能會被IObservable / Rx / Reactive框架完全取代,但只有經驗才會顯示出來。

我故意創造了一個新問題,給我空間來解釋我如何得到這個解決方案,因為它可能不會立即顯而易見。

有很多相關的問題,大多數人告訴你,如果你想以后能夠分離它們,你就不能使用內聯lambda:

確實,如果希望以后能夠分離它們,您需要保留對lambda的引用。 但是,如果您只是希望事件處理程序在訂閱者超出范圍時自行分離,則此答案適合您。

'答案

(如果您想了解我如何獲得此解決方案,請閱讀以下內容)

用法,給定帶有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類中,則直接調用subscriber.DoSomething(e)或直接調用DoSomething(e)。這樣做有效地創建了對訂閱者的引用,完全擊敗了對象...)

注意 :在某些情況下,這個CAN會在內存中引用為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限制為委托。

那時,我幾乎放棄了。 嘗試與C#規范作斗爭是沒有意義的。

但是,昨天,我從Reactive框架中發現了Observable.FromEvent方法,我沒有實現,但是用法似乎有點熟悉,而且非常有趣:

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

這是引起我注意的第一個論點。 這是缺少委托類型約束的解決方法。 我們通過傳遞將創建委托的函數來接受它。

將所有這些放在一起為我們提供了本答案頂部顯示的解決方案。

事后

我完全建議花時間了解反應框架(或最終被調用的內容)。 它非常有趣,而且有點令人費解。 我懷疑它也會使這樣的問題變得多余。

到目前為止,我見過的最有趣的東西是Channel9上的視頻。

如果您前往CodePlex,那么有一個名為Sharp Observation的項目,其中作者已經構建了一個良好的弱委托提供程序,在MSIL中實現。 快速,靈活,易於使用:例如

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

就這么簡單!

我一直在尋找一個解決方案很長一段時間,大多數都使用討厭的反思,但Benjohl的答案很棒。 我已經調整它以添加對非泛型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;
        }
    }
}

達斯汀坎貝爾的方法已經非常出色。 除了集成到.NET中的解決方案之外,唯一剩下的就是創建真正通用的弱事件處理程序的一種非常簡單的方法:

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM