Подписка на одноразовые события
Я уверен, что это невозможно, но я все равно спрошу.
Чтобы сделать однократную подписку на события, я часто нахожусь в использовании этого (самостоятельно изобретенного) шаблона:
EventHandler handler=null;
handler = (sender, e) =>
{
SomeEvent -= handler;
Initialize();
};
SomeEvent += handler;
Это довольно много котельной плиты, и это также заставляет Resharper зависеть от модифицированных закрытий. Есть ли способ превратить этот шаблон в метод расширения или аналогичный? Лучший способ сделать это?
В идеале мне бы хотелось что-то вроде:
SomeEvent.OneShot(handler)
Ответы
Ответ 1
Нелегко реорганизовать метод расширения, потому что единственный способ, которым вы можете ссылаться на событие на С#, - подписаться (+=
) на или отменить подписку (-=
) от него (если он не был объявлен в текущий класс).
Вы можете использовать тот же подход, что и в 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 */ });
Ответ 2
Следующий код работает для меня. Это не идеально, чтобы указать событие через строку, но у меня нет клея, как это решить. Я думаю, это невозможно в текущей версии С#.
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();
}
}
}
Ответ 3
Я бы предложил использовать "настраиваемое" событие, чтобы у вас был доступ к списку вызовов, а затем поднять событие, используя Interlocked.Exchange, чтобы одновременно читать и очищать список вызовов. При желании, подписка на события/отказ от подписки/повышение может быть выполнена поточно-безопасным способом с использованием простого стека связанных списков; когда событие будет поднято, код может после Interlocked.Exchange отменить порядок элементов стека. Для метода отмены подписки я бы предложил просто установить флаг в элементе списка-вызовы. Это может в принципе вызвать утечку памяти, если события были неоднократно подписаны и отписаны без события, когда-либо возникавшего, но это сделало бы для очень легкого потокобезопасного метода отмены подписки. Если кто-то хотел избежать утечки памяти, можно было бы подсчитать количество незарегистрированных событий в списке; если в списке добавлено слишком много отложенных событий, когда делается попытка добавить новый, метод добавления может пройти через список и удалить их. Все еще работоспособно в полностью незащищенном потокобезопасном коде, но сложнее.