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

发布于 2024-08-19 05:44:38 字数 480 浏览 6 评论 0原文

我发现自己经常做这种事情:-

 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

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

I find myself doing this sort of thing quite often:-

 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;

Basically I only want to attach an event handler to listen for one shot from the event, I no longer want to stay attached after that. Quite often that "snippert of code" is just one line.

My mind is going a bit numb, I'm sure there must be something I can do so I don't need to repeat all this overhead. Bear in mind that EventHandler may well be EventHandler<T>.

Any ideas how I can tidy up the repeative part of the code and just leave the snippet in a Lambda?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

鯉魚旗 2024-08-26 05:44:38

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

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

You could attache a permanent event handler to the event. The event handler then invokes "one shot event handlers" that are added to an internal queue:

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

Code:

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

Test class:

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);
    }
}
夜雨飘雪 2024-08-26 05:44:38

您可以使用反射:

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!"));

You can use reflection:

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

}

Use it like so:

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!"));
你的笑 2024-08-26 05:44:38

如果您可以使用 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();
        }
    }
}

If you can use the Reactive Extensions for .NET, you can simplify this.

You can make an Observable from an event, and only listen for the first element using .Take(1), to do your small snippet of code. This turns this entire process into a couple of lines of code.


Edit: In order to demonstrate, I've made a full sample program (I'll paste below).

I moved the observable creation and subscription into a method (HandleOneShot). This lets you do what you're attempting with a single method call. For demonstrating, I made a class with two properties that implements INotifyPropertyChanged, and am listening for the first property changed event, writing to the console when it occurs.

This takes your code, and changes it to:

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

Notice that all of the subscription/unsubscription happens automatically for you behind the scenes. There's no need to handle putting in the subscription manually - just Subscribe to the Observable, and Rx takes care of this for you.

When run, this code prints:

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

You only get a single, one shot trigger of your event.

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();
        }
    }
}
三五鸿雁 2024-08-26 05:44:38

另一位用户遇到一个非常相似的问题,我相信该线程中的解决方案适用于此处。

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

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

Another user encountered a very similar problem, and I believe the solution in that thread applies here.

In particular, what you have is not an instance of the publish/subscribe pattern, its a message queue. Its easy enough to create your own message queue using a Queue{EventHandler}, where you dequeue events as you invoke them.

So instead of hooking on to an event handler, your "one-shot" events should expose a method allowing clients to add an function to the message queue.

浮世清欢 2024-08-26 05:44:38

有效吗?如果是这样,那么我说就去吧。对于一个看起来相当优雅的一次性事件。

我喜欢什么...

  • 如果 s 被垃圾收集,那么事件处理程序也会被垃圾收集。
  • 分离代码位于附加代码旁边,可以轻松查看您在做什么。

您也许能够概括它,但我不太确定如何概括,因为我似乎无法获得指向事件的指针。

Does it work? If so, then I say go for it. For a one-shot event that looks to be quite elegant.

What I like...

  • If s is garbage collected, so will the event handler.
  • The detaching code is right next to the attaching code, making it easy to see what you are are doing.

You might be able to generalize it, but I'm not entierly sure how to because I can't seem to get a pointer to a event.

牛↙奶布丁 2024-08-26 05:44:38

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

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

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

Personally, I just create a specialized extension method for whatever type has the event I'm dealing with.

Here's a basic version of something I am using right now:

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

The reason I think this is the best solution is because you often don't need to just handle the event one time. You also often need to check if the event has already passed... For instance, here is another version of the above extension method that uses an attached property to check if the element is already loaded, in which case it just calls the given handler right away:

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;
        }
    }
}
晚雾 2024-08-26 05:44:38

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

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

You probably want to work with the new async/await idioms.
Usually when I need to execute an event handler one-shot like you described, what I really need is something like:

await variableOfSomeSort.SomeMethodAsync();
//small snippet of code here
往日情怀 2024-08-26 05:44:38

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

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

Why not do use the delegate stack built into the event?
Something like...

    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);
        }
    }
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文