在C#中使用Lambda的一次性事件

时间:2010-01-27 21:58:26

标签: c# events 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;

基本上我只想附加一个事件处理程序来监听事件中的一个镜头,之后我不想再保持联系。通常,“代码片段”只是一行。

我的思绪有点麻木,我确信必须有一些我可以做的事情,所以我不需要重复所有这些开销。请记住,EventHandler可能是EventHandler<T>

我有什么想法可以整理代码的重复部分并将代码段保留在Lambda中吗?

8 个答案:

答案 0 :(得分:8)

您可以使用反射:

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) => {
      eventInfo.RemoveEventHandler(eventSource, internalHandler);
      handler(src, args);
    };
    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) => {
      eventInfo.RemoveEventHandler(eventSource, internalHandler);
      handler(src, args);
    };
    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!"));

答案 1 :(得分:7)

您可以为事件附加永久事件处理程序。然后,事件处理程序调用添加到内部队列的“一次性事件处理程序”:

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 :(得分:6)

如果您可以使用Reactive Extensions for .NET,则可以简化此操作。

您可以制作Observable from an event,并且只使用.Take(1)收听第一个元素,以执行您的小段代码。这将整个过程变成几行代码。


编辑:为了演示,我制作了一个完整的示例程序(我将在下面粘贴)。

我将可观察的创建和订阅移动到方法(HandleOneShot)中。这使您可以通过单个方法调用执行您正在尝试的操作。为了演示,我创建了一个具有两个实现INotifyPropertyChanged属性的类,并且正在侦听第一个属性更改事件,并在控制台发生时写入。

这将获取您的代码,并将其更改为:

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();
        }
    }
}

答案 3 :(得分:2)

另一位用户遇到a very similar problem,我相信该线程中的解决方案适用于此处。

特别是,您所拥有的是发布/订阅模式的实例,它是一个消息队列。使用Queue{EventHandler}创建自己的消息队列非常容易,您可以在调用事件时将事件队列化。

因此,不是挂钩到事件处理程序,而是“一次性”事件应该公开一个允许客户端向消息队列添加函数的方法。

答案 4 :(得分:1)

有用吗?如果是这样,那么我说去吧。对于一次看起来非常优雅的一次性活动。

我喜欢什么...

  • 如果s是垃圾回收,事件处理程序也是如此。
  • 分离代码就在附加代码旁边,便于您查看自己在做什么。

你或许能够概括它,但我并不确定如何做到,因为我似乎无法获得指向事件的指针。

答案 5 :(得分:0)

就个人而言,我只是为我正在处理的事件创建一个专门的扩展方法。

这是我现在正在使用的基本版本:

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;
        }
    }
}

答案 6 :(得分:0)

您可能希望使用新的async / await惯用法。 通常当我需要像你描述的那样执行一次事件处理程序时,我真正需要的是:

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

答案 7 :(得分:0)

为什么不使用事件中内置的委托堆栈? 有点像...

    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);
        }
    }