2011-04-11 93 views
11

我相當確信這是不可能的,但我仍然會問。單次活動訂閱

爲了使單次認購活動,我經常發現自己使用這個(自己發明)模式:

EventHandler handler=null; 
handler = (sender, e) => 
{ 
    SomeEvent -= handler; 
    Initialize(); 
}; 
SomeEvent += handler; 

這是相當多的鍋爐板,並且這也使得ReSharper的關於修改後的關閉聲。有沒有將這種模式轉換爲擴展方法或類似方法的方法?一個更好的方法呢?

理想情況下,我想是這樣的:

SomeEvent.OneShot(handler) 
+0

我看不出爲什麼你不能把樣板代碼放入擴展方法。或者我沒有得到這個問題? – Achim 2011-04-11 15:33:23

+0

你如何通過取消訂閱事件?它有一個共同的基礎類型嗎?不是代表值類型(即,如果您以某種方式傳遞事件,則處理副本) – spender 2011-04-11 15:34:21

+0

@spender:所有代表都是引用類型。 – 2011-04-11 15:37:25

回答

4

這不是很容易重構爲一個擴展方法,因爲你可以參考在C#中的事件的唯一方式是通過訂閱(+=)至或從其取消訂閱(-=)(除非它在當前類中聲明)。

您可以使用與Reactive Extensions相同的方法:Observable.FromEvent需要兩個代表來訂閱該事件並取消訂閱。所以你可以這樣做:

public static class EventHelper 
{ 
    public static void SubscribeOneShot(
     Action<EventHandler> subscribe, 
     Action<EventHandler> unsubscribe, 
     EventHandler handler) 
    { 
     EventHandler actualHandler = null; 
     actualHandler = (sender, e) => 
     { 
      unsubscribe(actualHandler); 
      handler(sender, e); 
     }; 
     subscribe(actualHandler); 
    } 
} 

... 

Foo f = new Foo(); 
EventHelper.SubscribeOneShot(
    handler => f.Bar += handler, 
    handler => f.Bar -= handler, 
    (sender, e) => { /* whatever */ }); 
+0

不錯的嘗試,但所有的訂閱/取消訂閱的委託都是關於相同數量的樣板,但是使用一個不熟悉的界面,在第一次閱讀時不明顯。在用戶提供的代表中也沒有強制執行訂閱/取消訂閱,這意味着它很容易中斷,讓我擔心方法的名稱。謝謝,但我會堅持我的fttb :) – spender 2011-04-11 16:05:51

+0

我會說這是朝着正確方向邁出的一步。畢竟,代碼的意圖清楚地由方法名稱來表示,並且鍵入的代碼略少。這當然是一個開始。 – 2011-04-11 17:05:09

+1

+1,無論如何您都不能從調用方抽象出'f.Bar + =/- = handler',並且在擴展方法而不是處理程序中執行的訂閱/刪除可以被認爲是一個功能,而不是一個錯誤: ) – 2011-04-11 19:21:42

1

以下代碼適用於我。通過字符串指定事件並不完美,但我沒有膠水如何解決這個問題。我想現在的C#版本是不可能的。

using System; 
using System.Reflection; 

namespace TestProject 
{ 
    public delegate void MyEventHandler(object sender, EventArgs e); 

    public class MyClass 
    { 
     public event MyEventHandler MyEvent; 

     public void TriggerMyEvent() 
     { 
      if (MyEvent != null) 
      { 
       MyEvent(null, null); 
      } 
      else 
      { 
       Console.WriteLine("No event handler registered."); 
      } 
     } 
    } 

    public static class MyExt 
    { 
     public static void OneShot<TA>(this TA instance, string eventName, MyEventHandler handler) 
     { 
      EventInfo i = typeof (TA).GetEvent(eventName); 
      MyEventHandler newHandler = null; 
      newHandler = (sender, e) => 
          { 
           handler(sender, e); 
           i.RemoveEventHandler(instance, newHandler); 
          }; 
      i.AddEventHandler(instance, newHandler); 
     } 
    } 

    public class Program 
    { 
     static void Main(string[] args) 
     { 
      MyClass c = new MyClass(); 
      c.OneShot("MyEvent",(sender,e) => Console.WriteLine("Handler executed.")); 
      c.TriggerMyEvent(); 
      c.TriggerMyEvent(); 
     } 
    } 
} 
+0

剛發現,自定義事件訪問器也可能是一個選項。但這取決於您的偏好和要求。 – Achim 2011-04-11 21:45:48

1

我會建議使用「自定義」事件,使得你可以訪問調用列表,然後使用Interlocked.Exchange同時讀取和清除調用列表引發事件。如果需要,可以通過使用簡單的鏈接列表堆棧以線程安全的方式完成事件訂閱/取消訂閱/提升;當事件發生時,代碼可能會在Interlocked.Exchange之後顛倒堆棧項目的順序。對於取消訂閱方法,我可能會建議在調用列表項中簡單地設置一個標誌。如果事件被重複訂閱和取消訂閱,這在理論上可能會導致內存泄漏,而不會引發事件,但它會爲非常簡單的線程安全取消訂閱方法。如果想避免內存泄漏,可以保留列表中還有多少未訂閱的事件;如果在嘗試添加新事件時列表中有太多的未訂閱事件在列表中,則添加方法可以遍歷列表並將其刪除。仍然可以在完全無鎖的線程安全代碼中工作,但更復雜。