Подтвердить что ты не робот

Однократные события с использованием Lambda в С#

Я часто занимаюсь такими вещами: -

 EventHandler eh = null;  //can't assign lambda directly since it uses eh
 eh = (s, args) =>
 {
     //small snippet of code here

     ((SomeType)s).SomeEvent -= eh;
 }
 variableOfSomeType.SomeEvent += eh;

В основном, я хочу только привязать обработчик события для прослушивания одного снимка из события, после этого я больше не хочу оставаться прикрепленным. Довольно часто, что "snippert of code" - это всего лишь одна строка.

Мой ум немного онемел, я уверен, что я должен что-то сделать, поэтому мне не нужно повторять все эти накладные расходы. Имейте в виду, что EventHandler может быть EventHandler<T>.

Любые идеи, как я могу убрать повторяющуюся часть кода и просто оставить фрагмент в Лямбде?

4b9b3361

Ответ 1

Вы можете атташе - постоянный обработчик события. Обработчик события затем вызывает "обработчики событий одного выстрела", которые добавляются во внутреннюю очередь:

OneShotHandlerQueue<EventArgs> queue = new OneShotHandlerQueue<EventArgs>();

Test test = new Test();

// attach permanent event handler
test.Done += queue.Handle;

// add a "one shot" event handler
queue.Add((sender, e) => Console.WriteLine(e));
test.Start();

// add another "one shot" event handler
queue.Add((sender, e) => Console.WriteLine(e));
test.Start();

код:

class OneShotHandlerQueue<TEventArgs> where TEventArgs : EventArgs {
    private ConcurrentQueue<EventHandler<TEventArgs>> queue;
    public OneShotHandlerQueue() {
        this.queue = new ConcurrentQueue<EventHandler<TEventArgs>>();
    }
    public void Handle(object sender, TEventArgs e) {
        EventHandler<TEventArgs> handler;
        if (this.queue.TryDequeue(out handler) && (handler != null))
            handler(sender, e);
    }
    public void Add(EventHandler<TEventArgs> handler) {
        this.queue.Enqueue(handler);
    }
}

Класс тестирования:

class Test {
    public event EventHandler Done;
    public void Start() {
        this.OnDone(new EventArgs());
    }
    protected virtual void OnDone(EventArgs e) {
        EventHandler handler = this.Done;
        if (handler != null)
            handler(this, e);
    }
}

Ответ 2

Вы можете использовать отражение:

public static class Listener {

  public static void ListenOnce(this object eventSource, string eventName, EventHandler handler) {
    var eventInfo = eventSource.GetType().GetEvent(eventName);
    EventHandler internalHandler = null;
    internalHandler = (src, args) => {
      handler(src, args);
      eventInfo.RemoveEventHandler(eventSource, internalHandler);
    };
    eventInfo.AddEventHandler(eventSource, internalHandler);
  }

  public static void ListenOnce<TEventArgs>(this object eventSource, string eventName, EventHandler<TEventArgs> handler) where TEventArgs : EventArgs {
    var eventInfo = eventSource.GetType().GetEvent(eventName);
    EventHandler<TEventArgs> internalHandler = null;
    internalHandler = (src, args) => {
      handler(src, args);
      eventInfo.RemoveEventHandler(eventSource, internalHandler);
    };
    eventInfo.AddEventHandler(eventSource, internalHandler);
  }

}

Используйте его так:

variableOfSomeType.ListenOnce("SomeEvent", 
  (s, args) => Console.WriteLine("I should print only once!"));

variableOfSomeType.ListenOnce<InterestingEventArgs>("SomeOtherEvent", 
  (s, args) => Console.WriteLine("I should print only once!"));

Ответ 3

Если вы можете использовать Reactive Extensions for.NET, вы можете упростить это.

Вы можете сделать Наблюдаемое из события и прослушать только первый элемент с помощью .Take(1), чтобы сделать свой небольшой фрагмент кода, Это превращает весь этот процесс в пару строк кода.


Изменить: Чтобы продемонстрировать, я сделал полную примерную программу (я буду вставлять ниже).

Я переместил наблюдаемое создание и подписку в метод (HandleOneShot). Это позволяет делать то, что вы пытаетесь с помощью одного вызова метода. Для демонстрации я создал класс с двумя свойствами, который реализует INotifyPropertyChanged, и я слушаю событие с изменением свойства first, записывая его на консоль, когда это происходит.

Это берет ваш код и меняет его на:

HandleOneShot<SomeEventArgs>(variableOfSomeType, "SomeEvent",  e => { 
                    // Small snippet of code here
                }); 

Обратите внимание, что вся подписка/отмена подписки происходит автоматически для вас за кулисами. Нет необходимости обрабатывать подписку вручную - просто подпишитесь на Observable, и Rx позаботится об этом для вас.

При запуске этот код печатает:

Setup...
Setting first property...
 **** Prop2 Changed! /new val
Setting second property...
Setting first property again.
Press ENTER to continue...

Вы получаете только один триггер вашего события.

namespace ConsoleApplication1
{
    using System;
    using System.ComponentModel;
    using System.Linq;

    class Test : INotifyPropertyChanged
    {
        private string prop2;
        private string prop;
        public string Prop
        {
            get {
                return prop;
            }
            set
            {
                if (prop != value)
                {
                    prop = value;
                    if (PropertyChanged!=null)
                        PropertyChanged(this, new PropertyChangedEventArgs("Prop"));
                }
            }
        }

        public string Prop2
        {
            get
            {
                return prop2;
            }
            set
            {
                if (prop2 != value)
                {
                    prop2 = value;
                    if (PropertyChanged != null)
                        PropertyChanged(this, new PropertyChangedEventArgs("Prop2"));
                }
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;
    }


    class Program
    {
        static void HandleOneShot<TEventArgs>(object target, string eventName, Action<TEventArgs> action)  where TEventArgs : EventArgs
        {
            var obsEvent = Observable.FromEvent<TEventArgs>(target, eventName).Take(1);
            obsEvent.Subscribe(a => action(a.EventArgs));
        }

        static void Main(string[] args)
        {
            Test test = new Test();

            Console.WriteLine("Setup...");
            HandleOneShot<PropertyChangedEventArgs>(
                test, 
                "PropertyChanged", 
                e =>
                    {
                        Console.WriteLine(" **** {0} Changed! {1}/{2}!", e.PropertyName, test.Prop, test.Prop2);
                    });

            Console.WriteLine("Setting first property...");
            test.Prop2 = "new value";
            Console.WriteLine("Setting second property...");
            test.Prop = "second value";
            Console.WriteLine("Setting first property again...");
            test.Prop2 = "other value";

            Console.WriteLine("Press ENTER to continue...");
            Console.ReadLine();
        }
    }
}

Ответ 4

Другой пользователь столкнулся с очень похожей проблемой, и я считаю, что решение в этом потоке применяется здесь.

В частности, у вас есть не экземпляр шаблона публикации/подписки, его очередь сообщений. Его достаточно легко создать собственную очередь сообщений, используя Queue{EventHandler}, где вы деактивируете события при их вызове.

Поэтому вместо того, чтобы подключаться к обработчику событий, ваши события с одним выстрелом должны выставлять метод, позволяющий клиентам добавлять функцию в очередь сообщений.

Ответ 5

Это работает? Если так, то я говорю, иди за ним. Для случая с одним выстрелом, который выглядит довольно элегантным.

Что мне нравится...

  • Если s - сбор мусора, то будет обработчик события.
  • Отключающий код находится рядом с кодом присоединения, что позволяет легко видеть, что вы делаете.

Возможно, вы сможете обобщить его, но я не уверен, как это сделать, потому что я не могу получить указатель на событие.

Ответ 6

Лично я просто создаю специализированный метод расширения для любого типа, с которым я имею дело.

Вот базовая версия того, что я использую прямо сейчас:

namespace MyLibrary
{
    public static class FrameworkElementExtensions
    {
        public static void HandleWhenLoaded(this FrameworkElement el, RoutedEventHandler handler)
        {
            RoutedEventHandler wrapperHandler = null;
            wrapperHandler = delegate
            {
                el.Loaded -= wrapperHandler;

                handler(el, null);
            };
            el.Loaded += wrapperHandler;
        }
    }
}

Я думаю, что это лучшее решение, потому что вам часто не нужно просто обрабатывать событие один раз. Вам также часто нужно проверить, прошло ли событие... Например, вот еще одна версия вышеупомянутого метода расширений, который использует прикрепленное свойство, чтобы проверить, уже ли этот элемент загружен, и в этом случае он просто вызывает данный обработчик сразу:

namespace MyLibraryOrApplication
{
    public static class FrameworkElementExtensions
    {
        public static void HandleWhenLoaded(this FrameworkElement el, RoutedEventHandler handler)
        {
            if ((bool)el.GetValue(View.IsLoadedProperty))
            {
                // el already loaded, call the handler now.
                handler(el, null);
                return;
            }
            // el not loaded yet. Attach a wrapper handler that can be removed upon execution.
            RoutedEventHandler wrapperHandler = null;
            wrapperHandler = delegate
            {
                el.Loaded -= wrapperHandler;
                el.SetValue(View.IsLoadedProperty, true);

                handler(el, null);
            };
            el.Loaded += wrapperHandler;
        }
    }
}

Ответ 7

Вероятно, вы захотите работать с новыми идиомами async/wait. Обычно, когда мне нужно выполнить обработчик событий с одним выстрелом, как вы описали, мне действительно нужно что-то вроде:

await variableOfSomeSort.SomeMethodAsync();
//small snippet of code here

Ответ 8

Почему бы не использовать стек делегата, встроенный в событие? Что-то вроде...

    private void OnCheckedIn(object sender, Session e)
    {
        EventHandler<Session> nextInLine = null; 
        lock (_syncLock)
        {
            if (SessionCheckedIn != null)
            {
                nextInLine = (EventHandler<Session>)SessionCheckedIn.GetInvocationList()[0];
                SessionCheckedIn -= nextInLine;
            }
        }

        if ( nextInLine != null )
        {
            nextInLine(this, e);
        }
    }