web-dev-qa-db-ja.com

イベントが一度だけサブスクライブされるようにする方法

インスタンスのイベントの特定のクラスで1回だけサブスクライブするようにしたいと思います。

たとえば、次のことができるようにしたいと思います。

if (*not already subscribed*)
{
    member.Event += new MemeberClass.Delegate(handler);
}

このようなガードを実装するにはどうすればよいですか?

43
Glen T

ソースにアクセスできるクラスのイベントについて話している場合は、イベント定義にガードを配置できます。

private bool _eventHasSubscribers = false;
private EventHandler<MyDelegateType> _myEvent;

public event EventHandler<MyDelegateType> MyEvent
{
   add 
   {
      if (_myEvent == null)
      {
         _myEvent += value;
      }
   }
   remove
   {
      _myEvent -= value;
   }
}

これにより、1つのサブスクライバーだけが、イベントを提供するクラスのこのインスタンスのイベントにサブスクライブできるようになります。

[〜#〜] edit [〜#〜]上記のコードが悪いアイデアであり、スレッドセーフではない理由についてのコメントを参照してください。

クライアントの単一のインスタンスが複数回サブスクライブしている(そして複数のサブスクライバーが必要である)ことが問題である場合、クライアントコードはそれを処理する必要があります。だから交換

まだ購読されていません

イベントを初めてサブスクライブするときに設定されるクライアントクラスのブールメンバーを使用します。

編集(承認後):@Glen T(質問の送信者)からのコメントに基づいて、彼が行った承認済みソリューションのコードはクライアントクラス:

if (alreadySubscribedFlag)
{
    member.Event += new MemeberClass.Delegate(handler);
}

AlreadySubscribeFlagは、特定のイベントへの最初のサブスクリプションを追跡するクライアントクラスのメンバー変数です。ここで最初のコードスニペットを見る人は、@ Runeのコメントに注意してください。イベントのサブスクライブの動作を自明ではない方法で変更することはお勧めできません。

EDIT 2009/7/7:@Sam Saffronからのコメントを参照してください。すでに述べたとおり、Samはここで紹介する最初の方法はイベントサブスクリプションの動作を変更する賢明な方法ではないことに同意します。クラスのコンシューマーは、その動作を理解するために、その内部実装について知る必要があります。とても素敵ではありません。
@ Sam Saffronはスレッドの安全性についてもコメントしています。私は彼が2人のサブスクライバー(近い)が同時にサブスクライブしようとする可能性のある競合状態に言及していると想定しています。これを改善するためにロックを使用できます。イベントサブスクリプションの動作を変更する場合は、 サブスクリプションのプロパティをスレッドセーフに追加/削除する方法について をお勧めします。

38
Hamish Smith

念のため、これをすべての重複する質問に追加します。このパターンは私にとってうまくいきました:

myClass.MyEvent -= MyHandler;
myClass.MyEvent += MyHandler;

ハンドラーを登録するたびにこれを行うと、ハンドラーが1回だけ登録されることが保証されます。

62
alf

他の人が示したように、イベントの追加/削除プロパティをオーバーライドできます。または、イベントを破棄し、クラスのコンストラクター(またはその他のメソッド)で引数としてデリゲートを取得し、イベントを発生させる代わりに、提供されたデリゲートを呼び出すこともできます。

イベントは、誰でもサブスクライブできることを意味しますが、デリゲートはクラスに渡すことができるoneメソッドです。通常、ライブラリが提供する1対多のセマンティクスを実際に生成するときにのみイベントを使用する場合は、ライブラリのユーザーにとっておそらくそれほど驚くことではないでしょう。

7
jalf

UはPostsharperを使用して1つの属性を1回だけ書き込み、通常のイベントでそれを使用できます。コードを再利用します。コードサンプルを以下に示します。

[Serializable]
public class PreventEventHookedTwiceAttribute: EventInterceptionAspect
{
    private readonly object _lockObject = new object();
    readonly List<Delegate> _delegates = new List<Delegate>();

    public override void OnAddHandler(EventInterceptionArgs args)
    {
        lock(_lockObject)
        {
            if(!_delegates.Contains(args.Handler))
            {
                _delegates.Add(args.Handler);
                args.ProceedAddHandler();
            }
        }
    }

    public override void OnRemoveHandler(EventInterceptionArgs args)
    {
        lock(_lockObject)
        {
            if(_delegates.Contains(args.Handler))
            {
                _delegates.Remove(args.Handler);
                args.ProceedRemoveHandler();
            }
        }
    }
}

このように使用してください。

[PreventEventHookedTwice]
public static event Action<string> GoodEvent;

詳細は Postsharp EventInterceptionAspectを実装して、イベントハンドラーが2回フックされるのを防ぐ

5
Saghar

サブスクライブするかどうかを示す個別のフラグを保存するか、MemberClassを制御できる場合は、イベントのaddメソッドとremoveメソッドの実装を提供する必要があります。

class MemberClass
{
        private EventHandler _event;

        public event EventHandler Event
        {
            add
            {
                if( /* handler not already added */ )
                {
                    _event+= value;
                }
            }
            remove
            {
                _event-= value;
            }
        }
}

ハンドラーが追加されたかどうかを判断するには、_eventとvalueの両方でGetInvocationList()から返されたデリゲートを比較する必要があります。

3
Andrew Kennan

私は最近これをしました、そしてそれをここにドロップするのでそれはとどまります:

private bool subscribed;

if(!subscribed)
{
    myClass.MyEvent += MyHandler;
    subscribed = true;
} 

private void MyHandler()
{
    // Do stuff
    myClass.MyEvent -= MyHandler;
    subscribed = false;
}
0
Tony Steel