将变量捕获到EventHandler中

时间:2010-12-08 23:14:13

标签: c# .net events event-handling captured-variable

我可能会稍微过度思考这一点,但我可以帮助找出一种方法/最佳方式来做到以下几点。

我有一个附加到对象的事件处理程序,该对象是另一个类的属性。在我的事件处理程序中,我需要有关导致事件的对象的其他元数据(即包含它的对象的ID)。从发件人和事件信息中无法获得我需要的信息。我倾向于这是一个使用捕获变量的好地方,但我不确定我的实现想法。

因此,为了在代码中说明,我有一个事件处理程序:

void MyEventHandler(object sender, EventArgs e){
    //Do Stuff here
}

(作为一个注释,我在这里使用了基本的EventArgs,但在我的实际实现中,它是一个专门的子类,并且使用通用的EventHandler声明了事件)

我目前正在附上它:

topObject.SubObject.EventToHandle += MyEventHandler;

我稍后会这样解读它:

topObject.SubObject.EventToHandle -= MyEventHandler;

我在处理事件时想要topObject的ID,所以我要更改MyEventHandler以获得以下签名:

void MyEventHandler(int id, object sender, EventArgs e)

并附加事件处理程序,如下所示:

topObject.SubObject.EventToHandle += (s,e) => MyEventHandler(topObject.ID, s,e);

我对此的担忧有两方面。

  1. 在我附加到附加功能之外的情况下,处理程序实际上会在没有删除的情况下消失的范围存在问题。我曾经看到过一些奇怪的错误,当我使用lambda表达式时,事件处理程序在我身上消失了。它不是所有的时间,只是在某些情况下。任何人都可以告诉我这些情况可能是什么,所以我知道何时使用我的语法是安全的。
  2. 我记不清楚但是如果我使用这种语法,我认为我不能删除事件处理程序,因为创建的隐式对象不一样。
  3. 由于这两个问题,我的想法是创建一个Action并保存动作并使用它直到我需要删除事件处理程序。我做了以下事情:

    Action<object, EventArgs> handler = (s,e) => MyEventHandler(topObject.ID, s,e);
    topObject.SubObject.EventToHandle += handler;
    

    我知道该动作无法转换为事件处理程序。是否有一些简单的方法可以进行此转换,仍然可以确保我可以分离事件处理程序?我只是在想这个/有没有一种方法我现在没有看到这样做?

4 个答案:

答案 0 :(得分:1)

您可以创建包装现有事件处理程序的所有不错的包装函数,并为它们提供对象ID,但您仍然必须显式存储生成的委托以取消订阅事件。

我看到没有丑陋包装的唯一好办法就是使用反应式扩展。它本质上允许您将事件转换为IObservable,然后您可以将任何运算符应用于生成的IObservable(例如,Select将根据您的情况执行此操作)。但它仍然不那么优雅。

答案 1 :(得分:0)

引发事件的类中的事件处理程序签名应为:

protected void OnMyEvent(object sender, EventArgs  e)
{
    ....
}

protected void OnMyEvent(object sender, MyEventArgs  e)
{
    ....
}

在这种情况下,调用者会执行以下代码:

topObject.SubObject.MyEvent -= OnSubObjectMyEvent;

并像这样实现OnSubObjectMyEvent(示例):

private void OnSubObjectMyEvent(object sender, MyEventArgs e)
{
   int topObjectId = ((SubObjectType)sender).TopObject.Id;
   ...
}

这里我假设SubObject有一个TopObject属性,允许我获取顶级对象的id。

这就是大多数.NET Framework类的工作方式。这种方法有什么问题吗?

答案 2 :(得分:0)

如果事件处理程序需要top对象的Id,我猜它不会破坏你设计中的一些抽象层,让他们意识到彼此。

换句话说,您的事件处理程序可能如下所示:

void Handler(object sender, EventArgs e)
{
     var s = (SubObject) sender;
     int id = s.TopObject.ID;

     // do something with id...
}

我会将事件签名保留在sender和args的约定中。

答案 3 :(得分:0)

请勿更改活动的签名。虽然CLR在技术上允许事件的任何签名,但有理由为整个框架设计具有签名(object sender, EventArgs args)的事件。实际上,对于违反此签名的事件CA1009: Declare event handlers correctly

,存在FxCop规则
  

事件处理程序方法需要两个   参数。第一种是类型   System.Object并命名为'sender'。   这是提出的对象   事件。第二个参数是类型   System.EventArgs并命名为'e'。

有几种解决方案(替代方案):

  • 将topObject.ID作为自定义EventArgs的成员传递。
  • 创建一个封装topObject.ID的包装器对象,并将事件处理程序挂钩到此对象的方法。
  • 使用一个闭包范围,该范围可以保留对范围内topObject.ID的引用(这与上面的方法相同,但重量级提取是由编译器完成的)