VB.NET检测事件委托
这是一篇旧文档
看到这个标题,你一定觉得很疑惑,至少我们现在谈的这个问题比较生僻,你可能目前无法理解。
请先测试下面的两段代码,它们分别是C#和Vb的编码。
C#:
class mycls
{
delegate void DMyEvent(int i); //事件委托原型
event DMyEvent MyEvent; //声明事件
mycls(int i)
{
if (MyEvent != null) //检测事件
MyEvent(i); //激活事件
else
Console.WriteLine("事件事例委托为空!");
}
static void Main()
{
mycls mc = new mycls(5);
}
}
VB:Class mycls
Delegate Sub DMyEvent(ByVal i As Integer)
Event MyEvent As DMyEvent
Sub New(ByVal i As Integer)
' 这里的检测直接发生错误!
If MyEvent IsNot Nothing Then
RaiseEvent MyEvent(i)
Else
Console.WriteLine("事件事例委托为空!")
End If
End Sub
Shared Sub Main()
Dim mc As New mycls(5)
End Sub
End Class
很奇怪啊,我的代码明明写得就是完全一模一样的啊,为什么C#中通过的代码在VB中就发生了错误呢?!把 IsNot 换成 <> ???不行!那到底是发生了什么错误呢???这个问题让我百思不得其解,郁闷了很久。 没办法,只能反汇编看看,C#中的关键代码为:
ldfld class mycls/DMyEvent mycls::MyEvent
ldnull
ceq
很简单,加载类型中的字段,然后检测它是不是空类型。就这么简单,但为什么VB中就无法通过检测呢??? 嗯?奇怪?字段?!!ldfld 是加载字段的指令啊,MyEvent不是事件吗?
再仔细研究一下IL代码:
.field private class mycls/DMyEvent MyEvent
原来我们的MyEvent 竟然是一个继承自 DMyEvent 委托的字段?! 这个时候再在mycls的构造函数中看看激活事件的关键代码:
ldarg.1
callvirt instance void mycls/DMyEvent::Invoke(int32)
原来如此的感觉,所谓的事件,就是一个委托。当我们激活事件的时候,其实是在内部激活了一个委托而已。 而VB中的事件声明与激活形式与C#有非常大的区别,为了保持向前兼容(VB6遗留),所以它将事件作为了一种特殊的元素对待,对于激活事件必须使用 RaiseEvent 来激活,而无法直接激活。所以我们无法直接访问这个叫做“事件”的“字段”!!!
那么,VB就真的不能解决这个问题了吗?
不然,虽然处理起来极度的麻烦,但我们可以利用更深层次的事件处理来编写更加强大的VB事件!
下面的代码是可以检测事件委托的代码:
Class mycls
Delegate Sub DMyEvent(ByVal i As Integer)
Dim _MyEvent As DMyEvent
' 定义一个自处理事件
Custom Event MyEvent As DMyEvent
' 添加事件委托
AddHandler(ByVal value As DMyEvent)
_MyEvent = [Delegate].Combine(_MyEvent, value)
End AddHandler
' 移出事件委托
RemoveHandler(ByVal value As DMyEvent)
[Delegate].Remove(_MyEvent, value)
End RemoveHandler
' 激活事件处理
RaiseEvent(ByVal i As Integer)
If _MyEvent IsNot Nothing Then
_MyEvent(i)
End If
End RaiseEvent
End Event
Sub New(ByVal i As Integer)
' 这里我们可以直接检测私有字段委托
If _MyEvent IsNot Nothing Then
RaiseEvent MyEvent(i)
Else
Console.WriteLine("事件事例委托为空!")
End If
End Sub
Shared Sub Main()
Dim mc As New mycls(5)
End Sub
End Class
哇喔,代码一下子多了这么多! 我们使用 Custom Event 同样可以定义一个事件,它和属性非常相似,拥有三个子处理函数:添加委托、移出委托、激活委托!
我们的额外工作就是为传递给我们的委托进行存储及激活工作,可以使用 Dalegate 类型内的静态方法来处理。
而关于检测事件的委托是否为空,直接检测我们的私有字段 _MyEvent 就可以了!
仔细研究了一下C#的事件委托,与我们写得Vb方法如出一辙。
但是,如果我们委托的事件比较多怎么办?难道要写N个事件委托吗?而且如果每次多添加一个事件,那么就执行一次 Combine 方法进行合并,明显性能将会降低。
ComponentModel.EventHandlerList 类型解决你的难题,它是一个集合类型,用于添加事件委托。
你可以参考下面的代码来使用强大的事件自处理过程:
Class mycls
Delegate Sub DMyEvent(ByVal i As Integer)
Private events As New ComponentModel.EventHandlerList
Custom Event MyEvent1 As DMyEvent
AddHandler(ByVal value As DMyEvent)
events.AddHandler("MyEvent1", value)
End AddHandler
RemoveHandler(ByVal value As DMyEvent)
events.RemoveHandler("MyEvent1", value)
End RemoveHandler
RaiseEvent(ByVal i As Integer)
Dim e As DMyEvent = TryCast(events("MyEvent1"), DMyEvent)
If e IsNot Nothing Then
e.Invoke(i)
End If
End RaiseEvent
End Event
Custom Event MyEvent2 As DMyEvent
AddHandler(ByVal value As DMyEvent)
events.AddHandler("MyEvent2", value)
End AddHandler
RemoveHandler(ByVal value As DMyEvent)
events.RemoveHandler("MyEvent2", value)
End RemoveHandler
RaiseEvent(ByVal i As Integer)
Dim e As DMyEvent = TryCast(events("MyEvent2"), DMyEvent)
If e IsNot Nothing Then e.Invoke(i)
End RaiseEvent
End Event
Sub New(ByVal i As Integer)
RaiseEvent MyEvent1(i * 1)
mycls_MyEvent2(i * 2) '等同于 RaiseEvent MyEvent2(i * 2)
End Sub
Shared Sub Main()
Dim mc As New mycls(5)
End Sub
Private Sub mycls_MyEvent1(ByVal i As Integer) Handles Me.MyEvent1
MsgBox(i)
End Sub
Private Sub mycls_MyEvent2(ByVal i As Integer) Handles Me.MyEvent2
MsgBox(i)
End Sub
End Class
PS:两只王八穿马甲… -_-b本作品采用 知识共享署名-相同方式共享 4.0 国际许可协议 进行许可。