【问题标题】:VB.NET - Passing an event as a parameterVB.NET - 将事件作为参数传递
【发布时间】:2010-11-10 09:01:03
【问题描述】:

我需要将事件作为参数传递给函数。有没有办法做到这一点?

原因是我的程序中有两行代码序列,我在其中动态删除事件处理程序,然后再次设置处理程序。我正在为几个不同的事件和事件处理程序执行此操作,因此我决定编写一个执行此操作的函数。

例如,假设我的代码中有一个名为 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”部分给出了错误。

P.S:我已经在其他几个论坛上问过这个问题,并且一直有人问我为什么要在删除处理程序后立即设置它。原因是因为动态添加事件处理程序 n 次会导致处理程序在事件发生时执行 n 次。为了避免这种情况,也就是保证事件发生时handler只执行一次,我每次想动态添加handler时都先去掉handler。

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

无论如何,此时函数内发生的处理对我来说并不是最重要的,而只是将事件作为参数传递的方法。

【问题讨论】:

  • 你能告诉我们错误是什么吗?
  • 没有真正的方法可以以编程方式执行此操作,因为您无法将引用传递给事件成员。我也不确定 setHandler 应该做什么......你能解释一下为什么你需要删除一个事件处理程序,然后再重新添加它吗?
  • @cdhowie 我正在编辑我的帖子以解释为什么我需要这样做,被打断了,回来完成编辑并提交,然后才意识到你已经问过这个问题了.长话短说,原因现在包含在原帖中:)
  • 我明白了。不幸的是,真的没有办法做你在这里问的......

标签: vb.net events parameters


【解决方案1】:

当然你可以传递事件...你可以传递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 代码,但您可以在 3.5 中使用 AddressOf 来执行此操作,并且稍微麻烦一点。)

所以如果我有一个处理程序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 调用不在事件处理程序的订阅范围内。

享受吧!

【讨论】:

  • 如果我理解这一点,您传递的是Delegates,而不是Events 本身,正如问题中所问的那样。尽管如此,这是一个非常好的答案。
  • 谜,​​感谢您的详细回答。不过,我有点难以理解,因为我在您的帖子中看到了一些看起来很奇怪的结构(我第一次看到这样的语法,所以请与我交谈)。但是据我了解,我必须始终首先明确定义“ah”和“rh”中的对象和事件,对吗?我想知道的是,无论如何不将“ah”的定义限制为“combobox1.selectedIndexChanged”,这样我就可以使用相同的函数在不同的对象(cbx2)上设置处理程序,比如“ cbx2.selectedIndexChanged"。
  • @Tracer - 是的。只需将委托定义更改为Public Delegate Sub AddHandlerDelegate(Of T)(ByVal eh as T, comboBox as ComboBox),您就可以使其适用于任何ComboBox。如果您必须执行其他事件,我建议您调用委托AddSelectedIndexChangedHandler 等。为每个事件和控件添加和删除委托。
  • @Bobby - 事件本质上是代表 - Public Delegate Sub EventHandler(ByVal sender As Object, ByVal e As EventArgs)。我正在用另一位代表替换一位代表。实际上,我正在通过AddHandlerRemoveHandler 调用创建一个闭包,并为每个闭包传递一个委托。 :-)
  • @Enigmativity:停止将名称放入代码标签中,这会破坏@-comment-notifications。 ;) 是的,没错,这就是我的回答。 ;) 但是你不应该混合这个,如果你这样做或者如果你直接传递Event(如要求的那样),会有一个轻微而非常微妙的区别。无论如何+1。
【解决方案2】:

你不能传递事件。 Event 不是类型,它是一个关键字,定义了一对方法,添加和删除,用于更改类的事件成员的状态。在这方面,它们非常类似于属性(具有 get 和 set 方法)。

像属性一样,add 和 remove 方法可以做任何你想做的事情。通常,这些只会维护一个委托实例,它本身是一个MulticastDelegate,或者换句话说,如果引发事件,则一个被调用的委托列表。

在 C# 中可以清楚地看到这个结构,它没有被 AddHandler/RemoveHandler 屏蔽,而是直接编辑关联的处理程序列表:myObject.Event += new Delegate(...);

并且,再次像属性一样,作为实际上抽象两种不同方法的类的成员,不可能将事件作为对象直接传递。

【讨论】:

  • 感谢您的解释,鲍比。
【解决方案3】:

“事件”一词有多种不同的含义,具体取决于上下文。在您正在寻找的上下文中,事件是一对匹配的方法,它们将有效地从订阅列表中添加或删除委托。不幸的是,VB.NET 中没有类来表示没有附加对象的方法地址。可以使用reflection 来获取适当的方法,然后将这些方法传递给一个可以同时调用它们的例程,但在您的特定情况下这可能很愚蠢。

我可以看到在您描述的意义上传递事件可能有用的唯一情况是像 IDisposable 对象,它订阅来自寿命较长的对象的事件,并且需要取消订阅所有处理它时的事件。在这种情况下,使用反射为每个事件获取 remove-delegate 方法,然后在释放对象时调用所有 remove-delegate 方法可能会有所帮助。不幸的是,我不知道有什么方法可以表示要检索的事件,除非是字符串文字,直到运行时才能检查其有效性。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2010-10-04
    • 2013-10-30
    • 2015-03-10
    • 2020-06-22
    • 2016-08-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多