Подписка на одноразовое мероприятие - PullRequest
12 голосов
/ 11 апреля 2011

Я вполне уверен, что это невозможно, но я все же спрошу.

Чтобы сделать однократную подписку на события, я часто использую это-приобретенный) шаблон:

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

Это довольно много котельной плиты, и это также заставляет Решарпера гудеть от модифицированных крышек.Есть ли способ превратить этот шаблон в метод расширения или аналогичный?Лучший способ сделать это?

В идеале я хотел бы что-то вроде:

SomeEvent.OneShot(handler)

Ответы [ 3 ]

4 голосов
/ 11 апреля 2011

Реорганизовать метод расширения не очень просто, потому что единственный способ ссылаться на событие в 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 голос
/ 27 августа 2011

Я бы предложил использовать «настраиваемое» событие, чтобы у вас был доступ к списку вызовов, а затем вызвать событие с помощью Interlocked.Exchange, чтобы одновременно прочитать и очистить список вызовов. При желании подписка на событие / отмена подписки / повышение могут быть выполнены потокобезопасным способом с использованием простого стека связанных списков; при возникновении события код может после Interlocked.Exchange изменить порядок элементов стека. Для метода отказа от подписки я бы, вероятно, предложил просто установить флаг в элементе списка вызовов. Теоретически это может привести к утечке памяти, если события были неоднократно подписаны и отписаны без какого-либо события, но это сделало бы очень простой поточно-безопасный метод отписки. Если кто-то хочет избежать утечки памяти, он может вести подсчет того, сколько отписанных событий все еще находится в списке; если в списке слишком много отписанных событий при попытке добавить новое, метод add может просмотреть список и удалить их. По-прежнему работоспособен в поточно-ориентированном коде без блокировки, но более сложен

1 голос
/ 12 апреля 2011

Следующий код работает для меня. Не идеально указывать событие через строку, но я не знаю, как это решить. Я полагаю, что это невозможно в текущей версии 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();
        }
    }
}
...