如何跟踪各种事件的事件订阅
How to keep track of event subscriptions for various events
考虑下面的例子。此示例有一个简单的事件源 (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();
}
}
}
}
如何在 collection 中保存表单订阅的事件以及表单添加到每个事件的代表,以便在关闭表单之前删除它们?
简答:这是不可能的。您不能将事件存储在集合中并稍后清除它们的处理程序列表。
事件只有一个目的——封装。他们唯一做的就是提供访问器(即 add
和 remove
),因此 class 之外的代码只能 add/remove 支持委托字段的处理程序。这几段代码基本相同:
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
class 中,因为它是唯一可以访问底层委托的地方。您只能清除您自己声明的事件。
或者,您可以为每个事件存储一个单独的列表。
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;
}
}
}
}
考虑下面的例子。此示例有一个简单的事件源 (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();
}
}
}
}
如何在 collection 中保存表单订阅的事件以及表单添加到每个事件的代表,以便在关闭表单之前删除它们?
简答:这是不可能的。您不能将事件存储在集合中并稍后清除它们的处理程序列表。
事件只有一个目的——封装。他们唯一做的就是提供访问器(即 add
和 remove
),因此 class 之外的代码只能 add/remove 支持委托字段的处理程序。这几段代码基本相同:
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
class 中,因为它是唯一可以访问底层委托的地方。您只能清除您自己声明的事件。
或者,您可以为每个事件存储一个单独的列表。
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;
}
}
}
}