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



考虑示例bellow。该示例具有简单的事件源(股票)和一个表格,该表格通过更新表单标题的代表订阅了其事件(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();
            }
        }
    }
}

如何保存在一个集合中的事件中订阅的表单和代表通过表单添加到每个事件中的代表,以便可以在关闭表单之前将其删除?

简短答案:这是不可能的。您无法将活动存储在收藏中,并以后清除其处理程序列表。

事件仅具有一个目的 - 封装。他们唯一要做的就是提供访问者(即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;
            }
        }
    }
}

相关内容

  • 没有找到相关文章

最新更新