在创建句柄时,我是否使用正确的方法来监视我想要执行的任务?

时间:2009-10-12 14:14:35

标签: c# event-handling

是否有一种普遍接受的最佳做法来创建取消订阅的事件处理程序?

例如,我想出的第一件事就是:

// 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属性设置为truefalse

该技术通常运行良好,但有一些方法可以在创建ui组件的句柄之前触发事件。我试图保证除非已创建句柄,否则不会运行提供的处理程序。结果,我正在考虑提供一个有助于实现的一般助手类(“Bar”)。 Bar的目标是检查是否存在适当的句柄。如果是这样,太好了!如果没有,它将订阅适当的IsHandleCreated事件,以便在最终创建句柄时运行提供的处理程序。 (这很重要b / c客户端可以在句柄存在之前在UI的.ctor中设置它们的绑定。)但是,我希望这个订阅完全透明,所以我也希望每个事件处理程序自动取消订阅自己{ {1}}一旦完成运行。

我仍然在试图弄清楚这是不是一个好主意,所以我还没有概括这个概念 - 在这种情况下,我只是直接针对ToolStripItems实现它来验证这个想法很合理。不过,我还没有卖掉它。

我理解我还可以选择简单地强制只能在创建UI的句柄后,在表单的OnLoad事件(例如)中创建绑定。我知道那可行,我过去就做过。我想看看在这种情况下我是否可以放宽这个特殊要求。如果它甚至可行。

8 个答案:

答案 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;
}