2011-04-11 86 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之后颠倒堆栈项目的顺序。对于取消订阅方法,我可能会建议在调用列表项中简单地设置一个标志。如果事件被重复订阅和取消订阅,这在理论上可能会导致内存泄漏,而不会引发事件,但它会为非常简单的线程安全取消订阅方法。如果想避免内存泄漏,可以保留列表中还有多少未订阅的事件;如果在尝试添加新事件时列表中有太多的未订阅事件在列表中,则添加方法可以遍历列表并将其删除。仍然可以在完全无锁的线程安全代码中工作,但更复杂。