如何跟踪各种事件的事件订阅

问题描述 投票:0回答:1

考虑下面的例子。此示例有一个简单的事件源(Ticker)和一个通过更新表单标题的委托订阅其事件(Ticked)的表单。为简单起见,此示例只有一个事件,但请考虑多个表单订阅多个事件的情况。

using System;
using System.Collections.Generic;
using System.Threading;
using System.Windows.Forms;

namespace ConsoleApp3
{
    class Program
    {
        // event cannot be used as a key type
        // Dictionary<event, List<EventHandler>> subscriptions = new Dictionary<event, List<EventHandler>>();

        static void Main(string[] args)
        {
            Ticker ticker = new Ticker();
            Form form = new Form();
            form.Show();
            EventHandler eventHandler = new EventHandler((s, e) => {
                form.Invoke(new Action(() => { form.Text = DateTime.Now.ToString(); }));
            });
            // save a reference to the event and the delegate to be added
            //if (!subscriptions.ContainsKey(ticker.Ticked))
            //    subscriptions.Add(ticker.Ticked, new List<EventHandler>());
            //subscriptions[ticker.Ticked].Add(eventHandler);
            //form.FormClosing += (s, e) => {
            //    foreach (KeyValuePair<event, List<EventHandler>> subscription in subscriptions)
            //        foreach (EventHandler eventHandler in subscription.Value)
            //            subscription.Key -= eventHandler;
            //};
            //finally subscribe to the event(s)
            ticker.Ticked += eventHandler;
            Application.Run();
        }

        class Ticker
        {
            public event EventHandler Ticked;

            public Ticker()
            {
                new Thread(new ThreadStart(() => {
                    while (true)
                    {
                        Ticked?.Invoke(null, null);
                        Thread.Sleep(1000);
                    }
                })).Start();
            }
        }
    }
}

如何在集合中保存表单订阅的事件以及表单添加到每个事件的委托,以便在关闭表单之前删除它们?

c# events delegates event-handling eventhandler
1个回答
1
投票

简短回答:这是不可能的。您无法在集合中存储事件并在以后清除其处理程序列表。

事件仅用于一个目的 - 封装。他们唯一要做的就是提供访问器(即addremove),因此类外的代码只能在后台委托字段中添加/删除处理程序。这些代码基本相同:

class MyClass
{
    public event EventHandler MyEvent;
}
class MyClass
{
    private EventHandler myDelegate;

    public event EventHandler MyEvent
    {
        add => myDelegate += value;
        remove => myDelegate -= value;
    }
}

但是假设我们不使用事件,并直接使用委托。您可以创建一个字典,其中键是委托而不是事件,但这不适用于您的问题。这是因为委托是不可变的。您不能将委托存储在集合中,然后检索它并清除其调用列表。

这里唯一的解决方案是直接引用每个事件,就像在这段代码中一样。我不确定这个解决方案是否适合您。

using System;
using System.Threading;
using System.Windows.Forms;

namespace ConsoleApp3
{
    class Program
    {
        static void Main()
        {
            var ticker = new Ticker();
            var form = new Form();
            form.Show();

            form.FormClosing += (s, e) => ticker.ClearSubscriptions();

            ticker.Ticked += new EventHandler((s, e) => form.Invoke(
                new Action(() => form.Text = DateTime.Now.ToString())));

            Application.Run();
        }

        class Ticker
        {
            public event EventHandler Ticked;

            public Ticker()
            {
                new Thread(new ThreadStart(() => {
                    while (true)
                    {
                        Ticked?.Invoke(this, EventArgs.Empty);
                        Thread.Sleep(1000);
                    }
                })).Start();
            }

            public void ClearSubscriptions()
            {
                Ticked = null;
            }
        }
    }
}

如您所见,ClearSubscriptions手动清除Ticked事件。如果您有更多事件,则还必须手动清除它们,并且只能在Ticker类中清除它们,因为它是唯一可以访问基础委托的地方。您只能清除自己声明的事件。

或者,您可以为每个事件存储单独的列表。

static void Main()
{
    var ticker = new Ticker();
    var form = new Form();
    form.Show();

    var tickedSubscriptions = new List<EventHandler>();

    form.FormClosing += (s, e) =>
    {
        foreach (var subscription in tickedSubscriptions)
        {
            ticker.Ticked -= subscription;
        }

        tickedSubscriptions.Clear();
    };

    var handler = new EventHandler((s, e) => form.Invoke(
        new Action(() => form.Text = DateTime.Now.ToString())));

    tickedSubscriptions.Add(handler);
    ticker.Ticked += handler;

    Application.Run();
}

但在我看来,这个解决方案并不理想,因为你必须跟踪许多单独的列表。

更新:

我想到了另一个适合你案例的解决方案。我不确定它是否优雅。

即使委托是不可变的,也没有什么能阻止我们创建可以更改支持委托并将这些包装器放入字典的包装器对象。

using System;
using System.Collections.Generic;
using System.Threading;
using System.Windows.Forms;

namespace ConsoleApp3
{
    class Program
    {
        private static Dictionary<EventHandlerWrapper, List<EventHandler>> subscriptions =
        new Dictionary<EventHandlerWrapper, List<EventHandler>>();

        static void Main()
        {
            var ticker = new Ticker();
            var form = new Form();
            form.Show();

            form.FormClosing += (s, e) =>
            {
                foreach (var subscription in subscriptions)
                {
                    foreach (var handler in subscription.Value)
                    {
                        subscription.Key.Remove(handler);
                    }
                }

                subscriptions.Clear();
            };

            var updateTitle = new EventHandler((s, e) =>
                form.Invoke(new Action(() => form.Text = DateTime.Now.ToString())));

            ticker.Ticked += updateTitle;
            subscriptions.Add(ticker.TickedWrapper, new List<EventHandler> { updateTitle });

            Application.Run();
        }

        class Ticker
        {
            public event EventHandler Ticked;
            public EventHandlerWrapper TickedWrapper;

            public Ticker()
            {
                TickedWrapper = new EventHandlerWrapper(
                    () => Ticked,
                    handler => Ticked += handler,
                    handler => Ticked -= handler);

                new Thread(new ThreadStart(() => {
                    while (true)
                    {
                        Ticked?.Invoke(this, EventArgs.Empty);
                        Thread.Sleep(1000);
                    }
                })).Start();
            }
        }

        class EventHandlerWrapper
        {
            public Func<EventHandler> Get { get; }
            public Action<EventHandler> Add { get; }
            public Action<EventHandler> Remove { get; }

            public EventHandlerWrapper(
                Func<EventHandler> get,
                Action<EventHandler> add,
                Action<EventHandler> remove)
            {
                this.Get = get;
                this.Add = add;
                this.Remove = remove;
            }
        }
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.