2008-12-15 53 views
35

我想確保我只在一個實例的某個特定類中訂閱一次。如何確保一次活動只訂閱一次

比如我想能夠做到以下幾點:

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

我如何去實現這樣的後衛?

回答

31

如果您正在討論可以訪問源的類上的事件,那麼您可以將警衛置於事件定義中。

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

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

這將確保只有一個用戶可以訂閱的事件上提供該事件的類的實例。

編輯請查看關於爲什麼上面的代碼是一個壞主意,而不是線程安全的評論。

如果您的問題是客戶端的單個實例多次訂閱(並且您需要多個訂閱者),那麼客戶端代碼將需要處理該問題。所以更換

尚未訂閱

與當您訂閱該事件的第一次時設置客戶端類的布爾成員。

編輯(後接受):基於從@Glen T(問題的提交者)爲接受的解決方案,他跟去了代碼的註釋是在客戶端類:

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

在哪裏alreadySubscribedFlag是跟蹤特定事件的第一次訂閱的客戶端類中的成員變量。 查看第一個代碼片段的人請注意@ Rune的評論 - 以非明顯的方式改變訂閱活動的行爲並不是一個好主意。

編輯31/7/2009:請參閱@Sam Saffron的評論。正如我已經說過的,Sam同意這裏介紹的第一種方法不是修改事件訂閱行爲的明智方法。該班級的消費者需要了解其內部實施情況以瞭解其行爲。不大好。
@Sam Saffron也評論了線程安全。我假設他指的是兩個用戶(接近)同時嘗試訂閱的可能的競爭狀態,他們可能最終訂閱。鎖可以用來改善這一點。如果你打算改變事件訂閱的方式,那麼我建議你read about how to make the subscription add/remove properties thread safe

+0

我想我會繼續使用布爾成員變量的方法。 但是我有點驚訝,沒有其他方法來檢查客戶是否已經訂閱。我會認爲給定的客戶只想訂閱一次是比較常見的? – 2008-12-15 07:26:25

+1

根據您的設置,如果事件已經有用戶,您可能想要拋出異常。如果您在運行時添加訂戶,則會通知他們錯誤,而不是無所事事。在沒有通知用戶的情況下更改默認行爲並不是最佳做法。 – 2008-12-15 07:58:32

2

你要麼需要存儲一個獨立的標誌,指示你是否願意認購或者,如果你有過成員類的控制,提供了附加的實現,並刪除該事件的方法:

class MemberClass 
{ 
     private EventHandler _event; 

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

要決定是否添加了處理程序,您需要比較從_event和value中的GetInvocationList()返回的委託。

7

正如其他人所示,您可以覆蓋事件的添加/刪除屬性。或者,您可能想要放棄該事件,並且只需讓該類在其構造函數(或其他方法)中將一個委託作爲參數,而不是觸發該事件,則調用所提供的委託。

事件意味着任何人都可以訂閱他們,而代表是一個您可以傳遞給該類的方法。那麼,如果你只在事實上包含了它通常提供的一對多語義時才使用事件,那麼對於你的圖書館用戶來說,可能會不那麼令人驚訝。

47

我在所有重複問題中添加了這個,只是爲了記錄。這種模式爲我工作:

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

注意,這樣做,每次您註冊的處理程序將確保您的處理程序註冊一次。

4

U可以使用Postsharper只寫一次屬性並在正常事件中使用它。重用代碼。代碼示例如下。

[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; 

詳情看Implement Postsharp EventInterceptionAspect to prevent an event Handler hooked twice

0

在我看來,像一個簡單的方法來做到這一點是退訂處理程序(如果沒有訂閱,這將失敗默默)和然後訂閱。

member.Event -= eventHandler; 
member.Event += eventHandler; 

確實發生在開發商,這是錯誤的方式做到這一點負擔,但是對於快速和骯髒,這是快速和骯髒。