单次事件订阅

发布于 2024-11-01 05:32:56 字数 375 浏览 1 评论 0原文

我相当确信这是不可能的,但我还是要问。

为了对事件进行单次订阅,我经常发现自己使用这种(自行发明的)模式:

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

这是相当多的样板文件,而且它也让 Resharper 对修改后的闭包感到不满。有没有办法将此模式转变为扩展方法或类似方法?有更好的方法吗?

理想情况下,我想要这样的东西:

SomeEvent.OneShot(handler)

I'm fairly convinced that this isn't possible, but I'm going to ask nonetheless.

In order to make a single-shot subscription to events, I frequently find myself using this (self-invented) pattern:

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

It's quite a lot of boiler-plate, and it also makes Resharper whinge about modified closures. Is there a way of turning this pattern into an extension method or similar? A better way of doing it?

Ideally, I'd like something like:

SomeEvent.OneShot(handler)

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

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

发布评论

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

评论(3

苹果你个爱泡泡 2024-11-08 05:32:56

重构扩展方法并不容易,因为在 C# 中引用事件的唯一方法是订阅 (+=) 或取消订阅 (-= ) 来自它(除非它在当前类中声明)。

您可以使用与反应式扩展中相同的方法: 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 */ });

It's not very easy to refactor to an extension method, because the only way you can refer to an event in C# is by subscribing (+=) to or unsubscribing (-=) from it (unless it's declared in the current class).

You could use the same approach as in Reactive Extensions: Observable.FromEvent takes two delegates to subscribe to the event an unsubscribe from it. So you could do something like that:

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 */ });
左岸枫 2024-11-08 05:32:56

以下代码对我有用。必须通过字符串指定事件并不完美,但我不知道如何解决这个问题。我想这在当前的 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();
        }
    }
}

The following code works for me. It's not perfect to have to specify the event via a string, but I have no glue how to solve that. I guess it's not possible in the current C# version.

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();
        }
    }
}
不知在何时 2024-11-08 05:32:56

我建议使用“自定义”事件,以便您可以访问调用列表,然后使用 Interlocked.Exchange 引发事件以同时读取和清除调用列表。如果需要,可以使用简单的链表堆栈以线程安全的方式完成事件订阅/取消订阅/引发;当引发该事件时,代码可以在 Interlocked.Exchange 之后反转堆栈项的顺序。对于取消订阅方法,我可能建议简单地在调用列表项中设置一个标志。理论上,如果事件被重复订阅和取消订阅而事件从未被引发,这可能会导致内存泄漏,但这将成为一种非常简单的线程安全取消订阅方法。如果想避免内存泄漏,可以统计列表中仍有多少未订阅的事件;如果在尝试添加新事件时列表中有太多未订阅的事件,则 add 方法可以遍历列表并删除它们。在完全无锁的线程安全代码中仍然可行,但更复杂。

I would suggest using a "custom" event so that you have access to the invocation list, and then raise the event by using Interlocked.Exchange to simultaneously read and clear the invocation list. If desired, event subscription/unsubscription/raising could be done in thread-safe manner by using a simple linked-list stack; when the event is raised, the code could, after the Interlocked.Exchange, reverse the order of stack items. For the unsubscribe method, I'd probably suggest simply setting a flag within the invocation-list item. This could in theory cause a memory leak if events were repeatedly subscribed and unsubscribed without the event ever being raised, but it would make for a very easy thread-safe unsubscribe method. If one wanted to avoid a memory leak, one could keep a count of how many unsubscribed events are still in the list; if too many unsubscribed events are in the list when an attempt is made to add a new one, the add method could go through the list and remove them. Still workable in entirely lock-free thread-safe code, but more complicated.

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