web-dev-qa-db-ja.com

ラムダで使用するための弱いイベントハンドラーモデル

OK、これは質問というよりは答えですが、 この質問 を尋ね、 Dustin CampbellEgor からさまざまなビットをまとめた後=、そして「 IObservable/Rx/Reactiveframework 」からの最後のヒントもあります。私はこの特定の問題に対して実行可能な解決策を見つけたと思います。 IObservable/Rx/Reactiveフレームワークに完全に取って代わられる可能性がありますが、経験だけがそれを示します。

すぐにはわからないかもしれないので、この解決策にたどり着いた方法を説明するスペースを与えるために、意図的に新しい質問を作成しました。

多くの関連する質問がありますが、後でそれらを切り離すことができるようにしたい場合は、インラインラムダを使用できないことを最もよく伝えています。

そして、[〜#〜] you [〜#〜]が後でそれらをデタッチできるようにしたい場合は、参照を保持する必要があるのは事実です。あなたのラムダに。ただし、サブスクライバーがスコープ外になったときにイベントハンドラーを切り離したいだけの場合は、この答えが適しています。

39
Benjol

'答え

(私がこの解決策にたどり着いた方法を知りたい場合は、以下をお読みください)

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)を配置したり、Subscriberクラス内にいる場合はDoSomething(e)を直接呼び出したりします。これを行うと、サブスクライバーへの参照が効果的に作成されます。オブジェクトを完全に打ち負かします...)

:状況によっては、ラムダ用に作成されたラッピングクラスへの参照をメモリに残すことができますが、それらはバイトの重みしか持たないため、あまりにも気になりました。

実装:

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

ただし、私が示唆したように here 、Tをデリゲートに制約する方法がないため、これはコンパイルされません。

その時点で、私はほとんどあきらめました。 C#の仕様と戦おうとしても意味がありません。

しかし、昨日、ReactiveフレームワークからObservable.FromEventメソッドを発見しました。実装はありませんでしたが、使用法は少し馴染みがあり、非常に興味深いものでした。

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

それが私の注意を引いた最初の議論でした。これは、デリゲート型の制約がない場合の回避策です。デリゲートを作成する関数を渡すことでそれを取得します。

これらすべてをまとめると、この回答の上部に示されている解決策が得られます。

結果論

私は時間をかけてリアクティブフレームワーク(またはそれが最終的に呼ばれるもの)について学ぶことを徹底的にお勧めしました。それは非常に興味深く、少し驚異的です。また、このような質問は完全に冗長になると思います。

これまでのところ、私が見た中で最も興味深いものは Channel9 のビデオです。

35
Benjol

CodePlexに向かうと、 Sharp Observation というプロジェクトがあります。このプロジェクトでは、作成者がMSILに実装された優れた弱いデリゲートプロバイダーを構築しています。高速、柔軟性、使いやすさ:例:.

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

それと同じくらい簡単です!

5
Mark

私は長い間解決策を探していて、ほとんどが厄介な反射を使用していますが、ベンジョールの答えは素晴らしいです。 EventArgsから継承しない非ジェネリックEventHandler、DependencyPropertyChangedEventArgsのサポートを追加し、手動でイベントの登録を解除できるように調整しました。私は人々の考え、特にベンジョールに非常に興味があります。

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

ダスティンキャンベルのアプローチはすでに優れています。残っているのは、.NETに統合されたソリューションを保存することだけです。これは、非常に一般的な弱いイベントハンドラーを作成するための非常に簡単な方法です。

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

2
Stefan Dragnev