【问题标题】:Pass an event as a parameter to a method [duplicate]将事件作为参数传递给方法[重复]
【发布时间】:2011-12-06 22:36:27
【问题描述】:

可能重复:
How to pass an event to a method?

是否可以将事件作为参数传递给方法?

例如,下面的方法订阅事件,确实有效,然后取消订阅事件:

void SubscribeDoAndUnsubscribe<TElement, TEventArgs>(
        IEnumerable<TElement> elements,
        ??? elementEvent)
    where TEventArgs: EventArgs
{
    EventHandler<TEventArgs> handler = (sender, e) => { /* Handle an event */ };

    foreach (var element in elements)
    {
         // Subscribe somehow
         element.elementEvent += handler
    }

    // Do things

    foreach (var element in elements)
    {
         // Unsubscribe somehow
         element.elementEvent -= handler
    }
}

客户端代码:

var elements = new [] { new Button(), new Button() };
SubscribeDoAndUnsubscribe(elements, ??? /* e => e.Click */);

如果不可能,我如何以其他方式实现类似的逻辑?我应该为订阅/取消订阅方法传递一对委托吗?

【问题讨论】:

标签: c# events


【解决方案1】:

您实际上已经发现事件不是 C# 中的“头等舱”;您不能将事件作为数据传递。您可以通过制作委托将委托给与接收者关联的方法作为第一类对象。您可以将对任何变量的引用作为(大部分)一流对象传递。 (我说“主要”是因为对变量的引用不能存储在字段中,存储在数组中等等;与其他类型的数据相比,它们受到高度限制。)您可以通过 type 传递获取它的 Type 对象并传递它。

但是没有办法直接将与特定实例关联的事件、属性、索引器、构造函数或析构函数作为数据传递。您可以做的最好的事情是按照您的建议从 lambda 中创建一个委托(或一对委托)。或者,获取与事件关联的反射对象并将其与实例一起传递。

【讨论】:

  • 该规则有一个例外:当您传递的事件在进行此调用的同一类中声明时,您可以将事件作为参数传递给方法。如 A类 { public event SomeDelegate YourEvent; CallMe(SomeDelegate theEvent){theEvent(this,args); } }
  • @ILIABROUDNO:我看不出在你的小样本中YourEvent 被传递给任何东西。
  • 我的错。将 void 放在 CallMe 前面,然后向 A 类添加另一个方法: void OtherMethod(){CallMe(YourEvent);} 现在您将 YourEvent 作为参数传递。
  • 你不是在传递一个事件,你是在传递一个委托。
【解决方案2】:

不,很遗憾没有。

如果您查看Reactive Extensions,就会遇到类似的问题。他们使用的三个选项(IIRC - 我已经有一段时间没有看过了):

  • 传入对应的EventInfo,并通过反射调用
  • 传入事件的名称(必要时还有目标)并通过反射调用它
  • 为订阅和取消订阅传递代表

后一种情况的调用类似于:

SubscribeAndDoUnsubscribe(elements,
                          handler => e.Click += handler,
                          handler => e.Click -= handler);

声明将是:

void SubscribeDoAndUnsubscribe<TElement, TEventArgs>(
        IEnumerable<TElement> elements,
        Action<EventHandler<TEventArgs>> subscription,
        Action<EventHandler<TEventArgs>> unsubscription)
    where TEventArgs: EventArgs

【讨论】:

  • 您在示例中混合了 ClickClient
【解决方案3】:

您正在尝试绕过类型安全,如果不使用反射,您将无法做到这一点。我将向您展示一个更简单的示例来说明您正在尝试做的事情。

void DoSomethingOnSomethingElse(T obj, Action method)
{
    obj.method();
}

C# 不能以这种方式工作。编译器如何知道所有Ts 都有method 方法?它没有,也不可能。同样,并非代码中的每个TElement 都会有一个事件Click

听起来您只想在一组对象上设置一个单次使用事件处理程序。你可以很容易地做到这一点......

EventHandler handler = null;
handler = (s,e) => 
{
    DoSomething(e);
    var b = (Button) s;
    b.Click -= handler;
}

foreach (var button in buttons) 
{
    button.Click += handler;
}

这显然只适用于按钮,但在我写这篇文章时,我看到 Jon Skeet 向您展示了一个更通用的解决方案,所以我将在这里结束。

【讨论】:

  • 是的,关于类型安全和反射,你是对的。
猜你喜欢
  • 2013-07-01
  • 2011-04-30
  • 2011-07-21
  • 1970-01-01
  • 2013-01-03
  • 1970-01-01
  • 2010-10-04
  • 2014-04-22
  • 2018-12-11
相关资源
最近更新 更多