探索.NET中的事件机制

在我先前一篇博文中已经明确说明事件的本质(返回值)其实是一个委托,既然如此,它必定包含全部委托的特性和意义。我们可以通过简单地定义一个委托,然后把它声明成一个public event就等于公开了一个事件,可以在其它地方通过引用该对象而被订阅触发。通常而言,一个典型的自定义事件可能如下:

[C#]

public class MyExample
{
   public event Action MyEvent;
  
   public void RaiseMyEvent()
   {
       if(MyEvent!=null)
       {
              MyEvent();
       }
   }
}

//订阅者
MyExample mex = new MyExample();
mex.MyEvent+=(){//你自己的大量事件方法……};

[VB.NET]

Public Class MyExample
    Public Event MyEvent As Action

    Public Sub RaiseMyEvent()
        RaiseEvent MyEvent()
    End Sub
End Class

'订阅该事件
Dim mex As New MyExample()
AddHandler mex.MyEvent,Sub()
                                         '处理你自己的东西……
                                       End Sub

这是一个非常基本的雏形处理方法。不过我们可以这样思考——既然微软在类库中为我们提供了大量的委托(除了上面的Action之外,另外一个常见的便是EventHandler吧……或许很多人压根儿没有怎么好好关注它)。既然它们是“通用”委托,那么我们是否可以尝试着把凡是EventHandler(或者Action的)事件存在一起,同时又根据它们不同的时间类型进行“分门别类”归档呢(你自己想想看,如果你的类中具备多个EventHandler或是Action类型的事件,反复定义不感到累么?)本章节就将揭秘微软是如何处理此类情况的。

我们先使用Telerik的Just-DeCompiler工具反射查看一个普通的WinForm中Button控件的Click事件:

Button控件继承自ButtonBase(这个类是一个抽象类,用于描述一般“按钮类”控件的大致情形,因此如果要自定义某个按钮可继承此类,这里不展开讨论)。而ButtonBase又继承自Control类,Control类中包含了Click事件,反射后的代码如下:

[C#]

[SRCategory("CatAction")]
[SRDescription("ControlOnClickDescr")]
public event EventHandler Click
{
    add
    {
        base.Events.AddHandler(Control.EventClick, value);
    }
    remove
    {
        base.Events.RemoveHandler(Control.EventClick, value);
    }
}

[VB.NET]

<SRCategory("CatAction")> _
<SRDescription("ControlOnClickDescr")> _
Public Custom Event Click As EventHandler
    AddHandler
        MyBase.Events.AddHandler(Control.EventClick, value)
    End AddHandler
    RemoveHandler
        MyBase.Events.RemoveHandler(Control.EventClick, value)
    End RemoveHandler
End Event

可见这个事件并不是像我们原先一般简单定义一个event就可以了。这个定义貌似有些像“属性”——可以任意增加或者删除事件!的确如此!我们不要忘记了委托是可以+=或者-=的(因为任意一个委托都是继承自Delegate,而这个类具备了Combine和Remove方法,+=或者-=是一个语法糖而已,会自动被CLR转化成Combine或者是Remove)。事件也如此——一个默认的定义会被CLR自动转换成Add/Remove的方式。

继续跟踪发现base.Events的Events是一个EventHandlerList(.NET中XXXList肯定是一个列表集合),估计对一系列EventHandler 事件的实例全部添加/删除操作。

[C#]

[HostProtection(SecurityAction.LinkDemand, SharedState=true)]
public sealed class EventHandlerList : IDisposable
{
    private EventHandlerList.ListEntry head;

    private Component parent;

    public Delegate this[object key]
    {
        get
        {
            EventHandlerList.ListEntry listEntry = null;
            if (this.parent == null || this.parent.CanRaiseEventsInternal)
            {
                listEntry = this.Find(key);
            }
            if (listEntry == null)
            {
                return null;
            }
            else
            {
                return listEntry.handler;
            }
        }
        set
        {
            EventHandlerList.ListEntry listEntry = this.Find(key);
            if (listEntry == null)
            {
                this.head = new EventHandlerList.ListEntry(key, value, this.head);
                return;
            }
            else
            {
                listEntry.handler = value;
                return;
            }
        }
    }

    public EventHandlerList()
    {
    }

    internal EventHandlerList(Component parent)
    {
        this.parent = parent;
    }

    public void AddHandler(object key, Delegate value)
    {
        EventHandlerList.ListEntry listEntry = this.Find(key);
        if (listEntry == null)
        {
            this.head = new EventHandlerList.ListEntry(key, value, this.head);
            return;
        }
        else
        {
            listEntry.handler = Delegate.Combine(listEntry.handler, value);
            return;
        }
    }

    public void AddHandlers(EventHandlerList listToAddFrom)
    {
        for (EventHandlerList.ListEntry i = listToAddFrom.head; i != null; i = i.next)
        {
            this.AddHandler(i.key, i.handler);
        }
    }

    public void Dispose()
    {
        this.head = null;
    }

    private EventHandlerList.ListEntry Find(object key)
    {
        EventHandlerList.ListEntry listEntry = this.head;
        while (listEntry != null && listEntry.key != key)
        {
            listEntry = listEntry.next;
        }
        return listEntry;
    }

    public void RemoveHandler(object key, Delegate value)
    {
        EventHandlerList.ListEntry listEntry = this.Find(key);
        if (listEntry != null)
        {
            listEntry.handler = Delegate.Remove(listEntry.handler, value);
        }
    }

    private sealed class ListEntry
    {
        internal EventHandlerList.ListEntry next;

        internal object key;

        internal Delegate handler;

        public ListEntry(object key, Delegate handler, EventHandlerList.ListEntry next)
        {
            this.next = next;
            this.key = key;
            this.handler = handler;
        }
    }
}

[VB.NET]

<HostProtection(SecurityAction.LinkDemand, SharedState:=True)> _
Public NotInheritable Class EventHandlerList
    Implements IDisposable
    Private head As EventHandlerList.ListEntry

    Private parent As Component

    Default Public Property Item As Delegate(ByVal key As Object)
        Get
            Dim listEntry As EventHandlerList.ListEntry = Nothing
            If (Me.parent = Nothing OrElse Me.parent.CanRaiseEventsInternal) Then
                listEntry = Me.Find(key)
            End If
            If (listEntry = Nothing) Then
                Return Nothing
            Else
                Return listEntry.handler
            End If
        End Get
        Set(ByVal value As Delegate)
            Dim listEntry As EventHandlerList.ListEntry = Me.Find(key)
            If (listEntry = Nothing) Then
                Me.head = New EventHandlerList.ListEntry(key, value, Me.head)
                Return
            Else
                listEntry.handler = value
                Return
            End If
        End Set
    End Property

    Public Sub New()
        MyBase.New()
    End Sub

    Friend Sub New(ByVal parent As Component)
        MyBase.New()
        Me.parent = parent
    End Sub

    Public Sub [AddHandler](ByVal key As Object, ByVal value As Delegate)
        Dim listEntry As EventHandlerList.ListEntry = Me.Find(key)
        If (listEntry = Nothing) Then
            Me.head = New EventHandlerList.ListEntry(key, value, Me.head)
            Return
        Else
            listEntry.handler = Delegate.Combine(listEntry.handler, value)
            Return
        End If
    End Sub

    Public Sub AddHandlers(ByVal listToAddFrom As EventHandlerList)
        Dim i As EventHandlerList.ListEntry = listToAddFrom.head
        Do
            Me.AddHandler(i.key, i.handler)
            i = i.next
        Loop While i <> Nothing
    End Sub

    Public Sub Dispose()
        Me.head = Nothing
    End Sub

    Private Function Find(ByVal key As Object) As EventHandlerList.ListEntry 
        Dim listEntry As EventHandlerList.ListEntry = Me.head
        While listEntry <> Nothing AndAlso listEntry.key <> key
            listEntry = listEntry.next
        End While
        Return listEntry
    End Function

    Public Sub RemoveHandler(ByVal key As Object, ByVal value As Delegate)
        Dim listEntry As EventHandlerList.ListEntry = Me.Find(key)
        If (listEntry <> Nothing) Then
            listEntry.handler = Delegate.Remove(listEntry.handler, value)
        End If
    End Sub

    Private NotInheritable Class ListEntry
        Friend next As EventHandlerList.ListEntry

        Friend key As Object

        Friend handler As Delegate

        Public Sub New(ByVal key As Object, ByVal handler As Delegate, ByVal next As EventHandlerList.ListEntry)
            MyBase.New()
            Me.next = next
            Me.key = key
            Me.handler = handler
        End Sub
    End Class
End Class

果然不错,纵观整个EventHandlerList类是一个单链表类。其中每一个节点包含“Key”(用于判断究竟何种事件类型),"Handler"(某个事件的委托,之所以用Delegate是为了通用性强——任意的委托实体都可以传入其中),next指向下一个节点的引用(指针)。

现在让我们回过头来看看这样设计运行流程:

1)当我们对Button的Click事件进行+=的时候,也就是对Control类的Click进行了事件添加,那么也就是对Events实体进行操作(传入一个“事件类型”和一个事件委托具体指向的函数实体value)。

2)由于Events实体就是EventHandlerList对象,因而调用其方法Addhandler,首先判断Key是否存在,如果存在,那么直接对已有的委托实体进行Combine操作(多路委托,这就可以解释为什么一个事件绑定多个方法之后会按照顺序依次执行)。如果不存在,那么则生成一个新的节点,存放这个委托实体。

posted @ 2012-09-26 14:29  Serviceboy  阅读(619)  评论(0编辑  收藏  举报