将事件作为参数传递给方法

54

可能是重复问题:
如何将事件作为参数传递给方法?

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

例如,下面的方法订阅事件,完成工作并取消订阅事件:

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 */);
如果不可能,我该如何以其他方式实现类似的逻辑?我应该传递订阅/取消订阅方法的委托对吗?

请查看此链接:https://dev59.com/xVzUa4cB1Zd3GeqP7--k#8022448 - Felix K.
3个回答

56

事实上,您已经发现在C#中事件不是"一等公民"。您不能将一个事件作为数据进行传递。但是您可以通过创建委托将与接收器关联的方法的委托作为一等对象进行传递。您可以将任何变量的引用作为(大多数)一等对象进行传递。(我说“大多数”是因为与其他类型的数据相比,变量的引用受到严格限制,不能存储在字段中、不能存储在数组中等等。)您可以通过获取其Type对象并传递它来传递类型

但是,没有直接将与特定实例关联的事件、属性、索引器、构造函数或析构函数作为数据进行传递的方法。最好的方法是像您建议的那样,使用lambda表达式创建委托(或一对委托)。或者,获取与事件相关联的反射对象,并将其连同实例一起传递。


1
有一个例外规则:当你要传递的事件在调用此方法的同一类中声明时,你可以将事件作为参数传递给方法。例如:class A{ public event SomeDelegate YourEvent; CallMe(SomeDelegate theEvent){theEvent(this,args); } } - ILIA BROUDNO
@ILIABROUDNO:我没有看到在您的示例中YourEvent被传递给任何东西的地方。 - Eric Lippert
我的错。在CallMe前面加上void,然后在A类中添加另一个方法:void OtherMethod(){CallMe(YourEvent);} 现在你正在将YourEvent作为参数传递。 - ILIA BROUDNO
3
你没有传递事件,而是传递了代理。 - Softlion

39
很抱歉,不行。 如果你看Reactive Extensions,它也存在类似的问题。他们使用了三种选项(如果我没记错的话,因为我已经有一段时间没有看过): - 通过反射传递相应的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 - CodesInChaos
在 Reactive.Linq 中,它被称为 Observable.FromEventPattern - schoetbi

4

你试图规避类型安全检查,而这只能通过使用反射来实现。我将给你展示一个更简单的例子,说明你想要做的事情。

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

C#不是这样工作的。编译器怎么知道所有的 T 都有方法 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已经展示了一个更通用的解决方案,因此我在这里结束。

1
是的,你关于类型安全和反射的观点是正确的。 - altso

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接