インスタンスのイベントに対して、特定のクラスで 1 回だけサブスクライブするようにしたいと考えています。
たとえば、次のことができるようにしたいと考えています。
if (*not already subscribed*)
{
member.Event += new MemeberClass.Delegate(handler);
}
そのようなガードを実装するにはどうすればよいですか?
インスタンスのイベントに対して、特定のクラスで 1 回だけサブスクライブするようにしたいと考えています。
たとえば、次のことができるようにしたいと考えています。
if (*not already subscribed*)
{
member.Event += new MemeberClass.Delegate(handler);
}
そのようなガードを実装するにはどうすればよいですか?
記録のために、重複するすべての質問にこれを追加します。このパターンは私のために働いた:
myClass.MyEvent -= MyHandler;
myClass.MyEvent += MyHandler;
ハンドラーを登録するたびにこれを行うと、ハンドラーが一度だけ登録されるようになることに注意してください。
ソースにアクセスできるクラスのイベントについて話している場合は、イベント定義にガードを配置できます。
private bool _eventHasSubscribers = false;
private EventHandler<MyDelegateType> _myEvent;
public event EventHandler<MyDelegateType> MyEvent
{
add
{
if (_myEvent == null)
{
_myEvent += value;
}
}
remove
{
_myEvent -= value;
}
}
これにより、イベントを提供するクラスのこのインスタンスのイベントをサブスクライブできるサブスクライバーは 1 つだけになります。
編集上記のコードが悪い考えであり、スレッドセーフではない理由についてのコメントを参照してください。
クライアントの 1 つのインスタンスが複数回サブスクライブしている (複数のサブスクライバーが必要) という問題がある場合は、クライアント コードでそれを処理する必要があります。だから交換
まだ購読していません
初めてイベントをサブスクライブするときに設定されるクライアント クラスの bool メンバーを使用します。
編集(承認後): @Glen T(質問の提出者)からのコメントに基づいて、彼が行った承認済みソリューションのコードはクライアントクラスにあります:
if (alreadySubscribedFlag)
{
member.Event += new MemeberClass.Delegate(handler);
}
alreadySubscribeFlag は、特定のイベントへの最初のサブスクリプションを追跡するクライアント クラスのメンバー変数です。ここで最初のコード スニペットを見ている人は、@Rune のコメントに注意してください。イベントへのサブスクライブの動作を明白でない方法で変更することはお勧めできません。
EDIT 31/7/2009: @Sam Saffron からのコメントを参照してください。すでに述べたように、Sam も同意しますが、ここで提示された最初の方法は、イベント サブスクリプションの動作を変更する賢明な方法ではありません。クラスの消費者は、その動作を理解するために内部実装について知る必要があります。とても素敵ではありません。
@Sam Saffron もスレッドセーフについてコメントしています。彼は、2 つのサブスクライバー (に近い) が同時にサブスクライブを試み、両方ともサブスクライブする可能性がある競合状態について言及していると思います。これを改善するためにロックを使用できます。イベント サブスクリプションの動作方法を変更することを計画している場合は、サブスクリプションの追加/削除プロパティをスレッド セーフにする方法について読むことをお勧めします。
他の人が示したように、イベントの追加/削除プロパティをオーバーライドできます。または、イベントを破棄して、コンストラクター (または他のメソッド) でデリゲートを引数としてクラスに取り、イベントを発生させる代わりに、提供されたデリゲートを呼び出すこともできます。
イベントは誰でもサブスクライブできることを意味しますが、デリゲートはクラスに渡すことができるメソッドの1 つです。イベントが通常提供する 1 対多のセマンティクスが実際に必要な場合にのみイベントを使用する場合、ライブラリのユーザーにとっておそらくそれほど驚くことではないでしょう。
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 回フックされるのを防ぐを参照してください。
サブスクライブしたかどうかを示す別のフラグを保存するか、MemberClass を制御できる場合は、イベントの add メソッドと remove メソッドの実装を提供する必要があります。
class MemberClass
{
private EventHandler _event;
public event EventHandler Event
{
add
{
if( /* handler not already added */ )
{
_event+= value;
}
}
remove
{
_event-= value;
}
}
}
ハンドラーが追加されているかどうかを判断するには、GetInvocationList() から返されたデリゲートを _event と value の両方で比較する必要があります。