web-dev-qa-db-ja.com

イベントハンドラーが2回フックされるのを防ぐC#パターン

重複: イベントが一度だけサブスクライブされるようにする方法 および イベントハンドラーが既に追加されていますか?

私はいくつかのサービスを提供するシングルトンを持ち、私のクラスはいくつかのイベントにフックします。クラスがイベントに2回フックしてから2回呼び出されることもあります。これを防ぐための古典的な方法を探しています。どういうわけか、私はすでにこのイベントにフックしているかどうかを確認する必要があります...

90
Ali Shafai

イベントを明示的に実装し、呼び出しリストを確認します。また、nullを確認する必要があります。

using System.Linq; // Required for the .Contains call below:

...

private EventHandler foo;
public event EventHandler Foo
{
    add
    {
        if (foo == null || !foo.GetInvocationList().Contains(value))
        {
            foo += value;
        }
    }
    remove
    {
        foo -= value;
    }
}

上記のコードを使用すると、呼び出し元がイベントを複数回サブスクライブした場合、単に無視されます。

143

-=を使用して最初にイベントを削除するだけで、見つからない場合は例外はスローされません

/// -= Removes the event if it has been already added, this prevents multiple firing of the event
((System.Windows.Forms.WebBrowser)sender).Document.Click -= new System.Windows.Forms.HtmlElementEventHandler(testii);
((System.Windows.Forms.WebBrowser)sender).Document.Click += new System.Windows.Forms.HtmlElementEventHandler(testii);
165
PrimeTSS

各ソリューションをテストしましたが、最高の(パフォーマンスを考慮した)ソリューションは次のとおりです。

private EventHandler _foo;
public event EventHandler Foo {

    add {
        _foo -= value;
        _foo += value;
    }
    remove {
        _foo -= value;
    }
}

Linqを使用する必要はありません。サブスクリプションをキャンセルする前にnullをチェックする必要はありません(詳細については、MS EventHandlerを参照してください)。どこでも登録解除を行うことを覚えておく必要はありません。

33
LoxLox

これは、ソースレベルではなく、シンクレベルで処理する必要があります。つまり、イベントソースでイベントハンドラーロジックを規定しないでください-ハンドラー(シンク)自体にそれを残します。

サービスの開発者として、シンクは一度しか登録できないと言っているのは誰ですか?何らかの理由で2回登録したい場合はどうなりますか?また、ソースを変更することでシンクのバグを修正しようとしている場合、これらの問題をシンクレベルで修正する正当な理由です。

あなたには理由があると確信しています。重複するシンクが違法であるイベントソースは計り知れません。しかし、おそらくイベントのセマンティクスを損なわない代替アーキテクチャを検討する必要があります。

19
JP Alioto

イベントに追加および削除アクセサーを実装し、デリゲートのターゲットリストを確認するか、リストにターゲットを保存する必要があります。

Addメソッドでは、 Delegate.GetInvocationList メソッドを使用して、すでにデリゲートに追加されているターゲットのリストを取得できます。

デリゲートは、同じターゲットオブジェクトの同じメソッドにリンクされている場合、等しいと比較するように定義されているため、おそらくそのリストを実行して比較でき、等しいものが見つからない場合は、新しいものを追加します。

コンソールアプリケーションとしてコンパイルするサンプルコードを次に示します。

using System;
using System.Linq;

namespace DemoApp
{
    public class TestClass
    {
        private EventHandler _Test;

        public event EventHandler Test
        {
            add
            {
                if (_Test == null || !_Test.GetInvocationList().Contains(value))
                    _Test += value;
            }

            remove
            {
                _Test -= value;
            }
        }

        public void OnTest()
        {
            if (_Test != null)
                _Test(this, EventArgs.Empty);
        }
    }

    class Program
    {
        static void Main()
        {
            TestClass tc = new TestClass();
            tc.Test += tc_Test;
            tc.Test += tc_Test;
            tc.OnTest();
            Console.In.ReadLine();
        }

        static void tc_Test(object sender, EventArgs e)
        {
            Console.Out.WriteLine("tc_Test called");
        }
    }
}

出力:

tc_Test called

(つまり、一度だけ)

Microsoftの Reactive Extensions(Rx)フレームワーク は、「一度だけサブスクライブする」ためにも使用できます。

マウスイベントfoo.Clickedが与えられた場合、1つの呼び出しのみをサブスクライブして受信する方法は次のとおりです。

Observable.FromEvent<MouseEventArgs>(foo, "Clicked")
    .Take(1)
    .Subscribe(MyHandler);

...

private void MyHandler(IEvent<MouseEventArgs> eventInfo)
{
   // This will be called just once!
   var sender = eventInfo.Sender;
   var args = eventInfo.EventArgs;
}

RXアプローチでは、「一度サブスクライブする」機能に加えて、イベントを一緒に構成したり、イベントをフィルタリングしたりする機能が提供されます。それはかなり気の利いたものです。

イベントの代わりにアクションを作成します。クラスは次のようになります。

public class MyClass
{
                // sender   arguments       <-----     Use this action instead of an event
     public Action<object, EventArgs> OnSomeEventOccured;

     public void SomeMethod()
     {
          if(OnSomeEventOccured!=null)
              OnSomeEventOccured(this, null);
     }

}
2
Tono Nam

Silverlightでは、e.Handled = true;と言う必要があります。イベントコードで。

void image_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
    e.Handled = true; //this fixes the double event fire problem.
    string name = (e.OriginalSource as Image).Tag.ToString();
    DoSomething(name);
}

これが役立つ場合はチェックしてください。

0
Omzig

シングルトンオブジェクトに通知先のリストを確認させ、重複した場合は1回だけ呼び出します。または、可能であれば、イベント添付要求を拒否します。

0
Toby Allen