VB.NET - 将事件作为参数传递

12

我需要将事件作为参数传递给一个函数,有什么方法可以做到这一点吗?

我的程序中到处都是以下两行代码的序列,其中我动态地删除事件的处理程序,然后再次设置处理程序。我针对几个不同的事件和事件处理程序执行此操作,因此我决定编写一个函数来完成此操作。

例如,假设我的代码中有一个名为combobox1的组合框,以及一个名为indexChangedHandler的处理程序。在我的代码的多个位置,我都有以下两行:

RemoveHandler combobox1.SelectedIndexChanged, AddressOf indexChangedHandler
AddHandler combobox1.SelectedIndexChanged, AddressOf indexChangedHandler

现在,我不想在我的程序中一遍又一遍地重复上述两行代码(或类似的代码),所以我正在寻找一种方法来实现这个目标:

Private Sub setHandler(evt As Event, hndler As eventhandler)
     RemoveHandler evt, hndler
     AddHandler evt, hndler
End Sub

这样,在我程序中出现那两行代码(或类似代码)的所有位置,我都可以用以下代码进行替换:

setHandler(combobox1.SelectedIndexChanged, AddressOf indexChangedHandler)

目前,setHandler函数参数中"evt as Event"部分会引发错误。

附注:我在其他论坛上提出了这个问题,并且一直被问为什么要立即在移除处理程序后设置处理程序。原因是动态添加事件处理程序n次将导致事件发生时执行n次处理程序。 为了避免这种情况,也就是确保在事件发生时只执行一次处理程序,我首先每次想要动态添加处理程序时都会先删除处理程序。

您可能会问为什么首先会多次添加处理程序...原因是我只有在表单中特定事件E1发生之后(我在事件E1的处理程序内添加处理程序)才会添加处理程序。事件E1可以在我的表单中多次发生。如果我在再次添加处理程序之前不删除处理程序,则处理程序会被多次添加并因此执行多次。

总之,在此时,对于函数中发生的处理并不是最重要的,而是通过参数传递事件的方式。


1
你能告诉我们错误是什么吗? - Neil Knight
没有真正的编程方法来做到这一点,因为您无法传递对事件成员的引用。我也不确定setHandler应该做什么...您能澄清为什么需要删除事件处理程序,然后再次添加它吗? - cdhowie
@cdhowie 我正在编辑我的帖子,解释为什么我需要这样做,被打断了,回来后完成了编辑并提交,然后才意识到你已经问过这个问题了。长话短说,原因现在已经包含在原始帖子中 :) - Tracer
我明白了。不幸的是,在这里没有办法做你所要求的事情... - cdhowie
3个回答

16
当然,你可以传递事件... 你可以传递 Action(Of EventHandler) ,它可以做你想要的事情。
如果你有一个类定义了一个事件,像这样:
Public Class ComboBox
    Public Event SelectedIndexChanged As EventHandler   
End Class

给定一个ComboBox实例,您可以创建添加和删除处理程序操作,如下所示:

Dim combobox1 = New ComboBox()

Dim ah As Action(Of EventHandler)
    = Sub (h) AddHandler combobox1.SelectedIndexChanged, h
Dim rh As Action(Of EventHandler)
    = Sub (h) RemoveHandler combobox1.SelectedIndexChanged, h

现在,这是VB.NET 4.0的代码,但您可以使用AddressOf和一些其他操作来在3.5中完成此操作。

因此,如果我有一个处理程序Foo

Public Sub Foo(ByVal sender as Object, ByVal e As EventArgs)
    Console.WriteLine("Over here with Foo!")
End Sub

现在,在ComboBox上有一个Raise方法,我可以这样做:

ah(AddressOf Foo)
combobox1.Raise()
rh(AddressOf Foo)

这将按预期编写消息“在这里使用Foo!”。

我也可以创建此方法:

Public Sub PassActionOfEventHandler(ByVal h As Action(Of EventHandler))
    h(AddressOf Foo)
End Sub

我可以这样传递事件处理程序动作:
PassActionOfEventHandler(ah)
combobox1.Raise()
PassActionOfEventHandler(rh)

再次输出消息“在这里使用Foo!”

现在,可能会出现的一个问题是,在代码中意外交换添加和删除事件处理程序委托-毕竟它们是相同的类型。因此,很容易只定义强类型委托以进行添加和删除操作,如下所示:

Public Delegate Sub AddHandlerDelegate(Of T)(ByVal eh as T)
Public Delegate Sub RemoveHandlerDelegate(Of T)(ByVal eh as T)

除了委托类型,定义委托实例的代码不会改变:

Dim ah As AddHandlerDelegate(Of EventHandler)
    = Sub (h) AddHandler combobox1.SelectedIndexChanged, h
Dim rh As RemoveHandlerDelegate(Of EventHandler)
    = Sub (h) RemoveHandler combobox1.SelectedIndexChanged, h

现在,您可以非常有创意地使用此功能。您可以定义一个函数,该函数将采用添加处理程序委托、删除处理程序委托和事件处理程序,这将连接事件处理程序,然后返回给您一个IDisposable,您以后可以使用它来删除处理程序,而无需保留对事件处理程序的引用。这对于使用Using语句非常方便。

以下是签名:

Function SubscribeToEventHandler(
    ByVal h as EventHandler,
    ByVal ah As AddHandlerDelegate(Of EventHandler),
    ByVal rh As RemoveHandlerDelegate(Of EventHandler)) As IDisposable

所以,有了这个函数,现在我可以这样做:
combobox1.Raise()
Using subscription = SubscribeToEventHandler(AddressOf Foo, ah, rh)
    combobox1.Raise()
    combobox1.Raise()
End Using
combobox1.Raise()

这将会使消息“在这里,Foo!”仅被写入两次。第一个和最后一个Raise调用在事件处理程序的订阅之外。

享受吧!


1
如果我理解正确的话,你正在传递“委托”,而不是问题中所要求的“事件”本身。尽管如此,这是一个非常好的答案。 - Bobby
1
@Bobby - 事件本质上是委托 - Public Delegate Sub EventHandler(ByVal sender As Object, ByVal e As EventArgs)。我正在用另一个委托替换一个委托。实际上,我正在创建一个闭包,通过AddHandlerRemoveHandler调用,并针对每个闭包传递一个委托。 :-) - Enigmativity
@Enigmativity:如果你写@name,那个人将会被通知(如果他已经参与了讨论或者其他什么)。在名字周围使用代码标签会破坏这个功能。^^ 更多信息请参见http://meta.stackoverflow.com/questions/43019/how-do-comment-replies-work。 - Bobby
@Tracer - 非常感谢。我正在尝试编写一个库,通过传递一个IEvent<TEventArgs>来允许代码订阅事件,以使您的场景能够工作。这应该像您所希望的那样容易。目前只是在调查。或者,您可以查看反应式扩展 - http://social.msdn.microsoft.com/Forums/en-US/rx/threads - 它可以满足您的需求并提供更多功能,但需要您学习一种新的范例。 - Enigmativity
@Enigmativity,谢谢你的指引。我稍后会去看一下。祝你在图书馆事业中好运,如果可以的话,请保持联系。 - Tracer
显示剩余7条评论

3
您不能传递事件。 Event 不是一种类型,它是一个关键字,用于定义一对方法,add 和 remove,这些方法用于更改类的事件成员的状态。在这方面,它们非常像属性(具有 get 和 set 方法)。
与属性类似,add 和 remove 方法可以执行任何您想要的操作。通常,这些方法仅维护一个委托实例,该实例本身是一个 MulticastDelegate 或者换句话说,是一个被逐个调用的委托列表,如果引发了事件,则会按顺序调用这些委托。
在 C# 中,您可以清楚地看到这种结构,它不使用 AddHandler/RemoveHandler 掩盖,而是直接编辑相关处理程序的列表:myObject.Event += new Delegate(...);
并且,就像属性一样,作为实际抽象两种不同方法的类的成员,不可能将事件直接作为对象传递。

2
术语“事件”有许多不同的含义,具体取决于上下文。在您所寻找的情境中,“事件”是一对匹配的方法,可以有效地向订阅列表中添加或删除委托。不幸的是,在VB.NET中没有表示未附加对象的方法地址的类。可能可以使用反射来获取适当的方法,然后将这些方法传递给调用它们的例程,但在您特定的情况下,这可能是愚蠢的。
我唯一能想到可能有用的情况是传递一个事件,就像IDisposable对象订阅来自存活时间更长的对象的事件,并且需要在其被处理时取消订阅所有事件。在这种情况下,可能有助于使用反射来获取每个事件的移除委托方法,然后在对象被处理时调用所有移除委托方法。不幸的是,我不知道除了作为字符串文字以外如何表示要检索的事件,其有效性直到运行时才能检查。

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