I'm fairly convinced that this isn't possible, but I'm going to ask nonetheless.
In order to make a single-shot subscription to events, I frequently find myself using this (self-invented) pattern:
EventHandler handler=null;
handler = (sender, e) =>
{
SomeEvent -= handler;
Initialize();
};
SomeEvent += handler;
It's quite a lot of boiler-plate, and it also makes Resharper whinge about modified closures. Is there a way of turning this pattern into an extension method or similar? A better way of doing it?
Ideally, I'd like something like:
SomeEvent.OneShot(handler)
It's not very easy to refactor to an extension method, because the only way you can refer to an event in C# is by subscribing (+=
) to or unsubscribing (-=
) from it (unless it's declared in the current class).
You could use the same approach as in Reactive Extensions: Observable.FromEvent
takes two delegates to subscribe to the event an unsubscribe from it. So you could do something like that:
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 */ });
The following code works for me. It's not perfect to have to specify the event via a string, but I have no glue how to solve that. I guess it's not possible in the current C# version.
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();
}
}
}
I would suggest using a "custom" event so that you have access to the invocation list, and then raise the event by using Interlocked.Exchange to simultaneously read and clear the invocation list. If desired, event subscription/unsubscription/raising could be done in thread-safe manner by using a simple linked-list stack; when the event is raised, the code could, after the Interlocked.Exchange, reverse the order of stack items. For the unsubscribe method, I'd probably suggest simply setting a flag within the invocation-list item. This could in theory cause a memory leak if events were repeatedly subscribed and unsubscribed without the event ever being raised, but it would make for a very easy thread-safe unsubscribe method. If one wanted to avoid a memory leak, one could keep a count of how many unsubscribed events are still in the list; if too many unsubscribed events are in the list when an attempt is made to add a new one, the add method could go through the list and remove them. Still workable in entirely lock-free thread-safe code, but more complicated.
来源:https://stackoverflow.com/questions/5623658/single-shot-event-subscription