这是一篇旧文档

看到这个标题,你一定觉得很疑惑,至少我们现在谈的这个问题比较生僻,你可能目前无法理解。
请先测试下面的两段代码,它们分别是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