是否有一种普遍接受的最佳做法来创建取消订阅的事件处理程序?
例如,我想出的第一件事就是:
// Foo.cs
// ...
Bar bar = new Bar(/* add'l req'd state */);
EventHandler handler = new EventHandler(bar.HandlerMethod);
bar.HandlerToUnsubscribe = handler;
eventSource.EventName += handler;
// ...
// Bar.cs
class Bar
{
/* add'l req'd state */
// .ctor
public EventHandler HandlerToUnsubscribe { get; set; }
public void HandlerMethod(object sender, EventArgs args)
{
// Do what must be done w/ add'l req'd state
((EventSourceType)sender).EventName -= this.HandlerToUnsubscribe;
}
}
要说这种感觉不好/坏是轻描淡写。它与时间依赖紧密耦合(HandlerToUnsubscribe
必须在恰当的时间分配精确值)。在这种情况下,我觉得我必须扮演一个复杂的角色 - 我有什么愚蠢或简单的东西吗?
我正在创建UI和Winforms中的专有命令基础结构之间的绑定(使用System.Windows.Input中有用的ICommand
)。绑定基础结构的一个方面是,在UI命令组件(如工具栏按钮或菜单项)之间创建绑定的用户可以选择侦听命令的CanExecuteChanged
事件,然后根据该事件更新UI的状态。 - 通常将Enabled属性设置为true
或false
。
该技术通常运行良好,但有一些方法可以在创建ui组件的句柄之前触发事件。我试图保证除非已创建句柄,否则不会运行提供的处理程序。结果,我正在考虑提供一个有助于实现的一般助手类(“Bar
”)。 Bar
的目标是检查是否存在适当的句柄。如果是这样,太好了!如果没有,它将订阅适当的IsHandleCreated
事件,以便在最终创建句柄时运行提供的处理程序。 (这很重要b / c客户端可以在句柄存在之前在UI的.ctor中设置它们的绑定。)但是,我希望这个订阅完全透明,所以我也希望每个事件处理程序自动取消订阅自己{ {1}}一旦完成运行。
我仍然在试图弄清楚这是不是一个好主意,所以我还没有概括这个概念 - 在这种情况下,我只是直接针对ToolStripItems实现它来验证这个想法很合理。不过,我还没有卖掉它。
我理解我还可以选择简单地强制只能在创建UI的句柄后,在表单的OnLoad事件(例如)中创建绑定。我知道那可行,我过去就做过。我想看看在这种情况下我是否可以放宽这个特殊要求。如果它甚至可行。
答案 0 :(得分:11)
格雷格,
你拥有的不是一个观察者模式,而是一个消息队列。所以你只是使用错误的设计模式来解决你想要解决的问题。
使用Queue{Action{object}}
从头开始实现自己的消息队列非常容易,其中对象会自行排队,并且只需在调用项目时将项目出列。
答案 1 :(得分:1)
通常的方法是存储一个布尔值,以确定它是否应该运行...
bool runMyEvent = true;
void Handler(object sender, EventArgs e) {
if (runMyEvent) {
// handler here
runMyEvent = false;
} else {
return;
}
}
答案 2 :(得分:1)
只运行一次的处理程序怎么样? 像这样:
if (wasRun) return; wasRun = true;
答案 3 :(得分:1)
你可以在这里使用run once方法,这里提到过几次,但是根据你的使用情况,有一些问题。
1)您可能希望稍后再次挂钩该方法,并让它再次运行。虽然我想你可以重置你的bool
2)你仍然有这样的参考,最终可能会把你的班级留在记忆中,而不是被垃圾收集。
一种选择是在定义事件处理时使用匿名方法和闭包:
public class Foo
{
public EventHandler<EventArgs> MyEvent;
public void FireEvent()
{
if(MyEvent != null)
MyEvent(this, EventArgs.Empty);
}
}
Foo obj = new Foo();
Action<object, EventArgs> action = new Action<object, EventArgs>((sender, args) =>
{
// We're now running the event handler, so unsubscribe
obj.MyEvent -= new EventHandler<EventArgs>(action);
// Do whatever you wanted to do when the event fired.
});
obj.MyEvent += new EventHandler<EventArgs>(action);
答案 4 :(得分:0)
如果它有意义(如果您有权访问调用处理程序的代码),也许您可以在运行handler后删除所有事件。
我不知道这是不是一个好主意,只是另一种想法。
答案 5 :(得分:0)
您可以使用WeakReference对象来介绍弱订阅者。在事件触发期间,您可以检查是否已收集弱引用,并在必要时从订阅者列表中删除此订阅者。
这背后的基本思想是,当订阅者收集GC时,您的处理程序会注意到它并将其从订阅者列表中抛弃。
答案 6 :(得分:0)
有一篇关于CodeProject的文章:Weak events,它涉及这个问题的几种解决方案。
答案 7 :(得分:0)
我通常会执行以下操作来实现1次事件处理程序。
void OnControlClickOneTime(this Control c, EventHandler e) {
EventHandler e2 = null;
e2 = (s,args) => {
c.Click -= e2;
e(s,args);
};
c.Click += e2;
}