我相信这是不可能的,但我仍然会问。
为了对事件进行单次订阅,我经常发现自己使用这种(自我发明的)模式:
EventHandler handler=null;
handler = (sender, e) =>
{
SomeEvent -= handler;
Initialize();
};
SomeEvent += handler;
这是相当多的锅炉板,它也使Resharper对改装后的瓶盖抱怨。有没有办法将此模式转换为扩展方法或类似方法?这样做的更好方法是什么?
理想情况下,我喜欢这样的东西:
SomeEvent.OneShot(handler)
答案 0 :(得分: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 */ });
答案 1 :(得分: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();
}
}
}
答案 2 :(得分:1)
我建议使用“自定义”事件,以便您可以访问调用列表,然后使用Interlocked.Exchange同时读取和清除调用列表来引发事件。如果需要,可以使用简单的链表列表以线程安全的方式完成事件订阅/取消订阅/提升;引发事件时,代码可以在Interlocked.Exchange之后反转堆栈项的顺序。对于取消订阅方法,我可能建议只在invocation-list项中设置一个标志。理论上,如果事件在没有引发事件的情况下重复订阅和取消订阅,则理论上可能会导致内存泄漏,但这会使得非常简单的线程安全取消订阅方法成为可能。如果想要避免内存泄漏,可以计算列表中仍有多少未订阅事件;如果在尝试添加新的事件时列表中有太多未订阅的事件,则add方法可以通过列表并删除它们。仍然可以使用完全无锁的线程安全代码,但更复杂。