首页 > 代码库 > .Net事件探索

.Net事件探索

【原】.Net事件探索

对自定义事件的疑惑

我创建了一个 "EventTest.Book" 类,包含一个 "Name" 属性和一个 "NameChanged" 事件。在类外部判断 "NameChanged" 事件的委托是否为空时,编辑器提示“错误: 事件 "EventTest.BookNameChanged" 只能出现在 += 或 -= 的左边(从类型 "EventTest.Book" 中使用除外)”:(图1所示),然而在类内部判断 "NameChanged" 事件的委托是否为空却没有任何问题(图2所示)。为什么这个事件的委托只能在类的内部访问,却不能在外部访问,难道是 "NameChanged" 事件的访问修饰符没有定义成 "public",造成在类的外部不能访问,我又看了一下 "NameChanged" 事件的定义  public event EventHandler NameChanged;  没有任何问题。

图1

图2

为什么在类的内部能够对自定义事件进行关系运算,而在外部不能进行关系运算呢?

探索

我用.Net Reflector反编译生成的文件,反编译后发现编译器为我们做了不少工作,主要有两个方面:

  1. 为事件增加了私有的、和事件名称相同的字段
  2. 为事件增加了 add 、remove 访问函数
//定义字段用于存储委托private EventHandler NameChanged;//增加 add 和 remove 访问函数public event EventHandler NameChanged{    add    {        EventHandler handler;        EventHandler handler2;        EventHandler handler3;        bool flag;        handler = this.NameChanged;    Label_0007:        handler2 = handler;        handler3 = (EventHandler) Delegate.Combine(handler2, value);        handler = Interlocked.CompareExchange<EventHandler>(&this.NameChanged, handler3, handler2);        if (((handler == handler2) == 0) != null)        {            goto Label_0007;        }        return;    }    remove    {        EventHandler handler;        EventHandler handler2;        EventHandler handler3;        bool flag;        handler = this.NameChanged;    Label_0007:        handler2 = handler;        handler3 = (EventHandler) Delegate.Remove(handler2, value);        handler = Interlocked.CompareExchange<EventHandler>(&this.NameChanged, handler3, handler2);        if (((handler == handler2) == 0) != null)        {            goto Label_0007;        }        return;    }}

思考

回到我开头提出的问题“为什么在类的内部能够对自定义事件进行关系运算,而在外部不能进行关系运算呢?”。

第一个问题的原因:在类内部进行关系运算时,参与运算的是编译器为我们自动增加的私有字段,而不是我们定义的事件。既然是字段肯定可以进行关系运算。

第二个问题的原因:在外部只能访问事件的访问控制函数( add 和 remove 函数),对事件的 += 或 -= 操作其本质就是访问 add 或 remove 控制函数;对于编译器自动增加的私有字段自然不能访问了。

总结及延伸

回忆类的定义:类是现实世界或思维世界中的实体在计算机中的反映,是对数据和操作的封装。我认为面向对象只有三个基本元素:字段、函数和类;字段就是数据,函数就是操作,类就是对字段和函数的封装。C#中许多的元素就是由这三个基本元素组成。

总结:事件的本质

事件的本质就是:存储委托的字段和访问这些委托的 add 和 remove 函数。

延伸1:属性的本质是什么

属性的本质:字段 和 访问这些字段的函数。

延伸2:委托是什么

通过.Net Reflector 反编译mscorlib库,发现委托是类。定义一个委托其实就是定义一个继承自 System.MulticastDelegate 的类。我们来看看常用的委托 EventHandler 的继承关系:

System.Object
  System.Delegate
    System.MulticastDelegate
      System.EventHandler

我们通过代码验证一下,在程序中写这段代码,是能够通过编译的。

Console.WriteLine((this.NameChanged as MulticastDelegate).ToString());

我们自定义的委托也继承自 System.MulticastDelegate 。

需要注意的是: Delegate 和 delegate 是不同的。Delegate 是所有委托的基类;delegate 用于定义委托的关键字。

延伸3:System.ComponentModel.Component事件是如何定义的

Component类并没有为每个事件定义一个私有的同名字段,而是定义了一个 Events 属性用于存储绑定到事件的委托,定义如下:

protected EventHandlerList Events { get; }

这样做的好处是大大节省了为事件分配存储空间的开销。按默认的模式对象的所有事件都要分配一个字段用于存储委托,无论事件是否绑定都要分配存储空间,应用程序中存在大量的组件可想而知存储开销由多大。微软的工程师们为每个组件的对象创建了一个 EventHandlerList 类型的属性用于存储绑定到事件的委托,如果没有绑定到事件的委托就不用为该事件分配存储空间,因此做到了按需分配。

那么Component类是如何定义事件的呢,通过反编译我们来看一下 Control 类的 TextChanged 是如何定义的:

[SRCategory("CatPropertyChanged"), SRDescription("ControlOnTextChangedDescr")]public event EventHandler TextChanged{    add    {        base.Events.AddHandler(EventText, value);        return;    }    remove    {        base.Events.RemoveHandler(EventText, value);        return;    }} 
private static readonly object EventText;

 下面我们写一段代码用于获取组件对象的某个委托:

/// <summary>/// 通过反射获取组件对象的某个事件的委托。/// </summary>/// <param name="com">组件对象</param>/// <param name="eventKey">事件索引关键字段名称</param>/// <returns>该对象指定事件的委托</returns>public static Delegate GetComponentDelegate(Component com, string eventKey){    //通过反射获取 Events 的属性信息    PropertyInfo pi = com.GetType().GetProperty("Events", BindingFlags.Instance | BindingFlags.NonPublic);    //获取 com 对象的 Events 的值    EventHandlerList ehl = (EventHandlerList)pi.GetValue(com, null);    //获取事件索引字段    Type type = com.GetType();    FieldInfo fi = null;    do    {        fi = type.GetField(eventKey, BindingFlags.Static | BindingFlags.NonPublic);        type = type.BaseType;    } while (type.FullName.ToLower() != "system.object" && fi == null);    //获取委托    if (fi != null)    {        object key = fi.GetValue(null);        return ehl[key];    }    else    {        return null;    }}

其他

System.Windows.Forms.Control事件索引关键字字段名称

#region System.Windows.Forms.Control事件索引关键字字段名称/*EventAutoSizeChangedEventBackColorEventBackgroundImageEventBackgroundImageLayoutEventBindingContextEventCausesValidationEventChangeUICuesEventClickEventClientSizeEventContextMenuEventContextMenuStripEventControlAddedEventControlRemovedEventCursorEventDockEventDoubleClickEventDragDropEventDragEnterEventDragLeaveEventDragOverEventEnabledEventEnabledChangedEventEnterEventFontEventForeColorEventGiveFeedbackEventGotFocusEventHandleCreatedEventHandleDestroyedEventHelpRequestedEventImeModeChangedEventInvalidatedEventKeyDownEventKeyPressEventKeyUpEventLayoutEventLeaveEventLocationEventLostFocusEventMarginChangedEventMouseCaptureChangedEventMouseClickEventMouseDoubleClickEventMouseDownEventMouseEnterEventMouseHoverEventMouseLeaveEventMouseMoveEventMouseUpEventMouseWheelEventMoveEventPaddingChangedEventPaintEventParentEventPreviewKeyDownEventQueryAccessibilityHelpEventQueryContinueDragEventRegionChangedEventResizeEventRightToLeftEventSizeEventStyleChangedEventSystemColorsChangedEventTabIndexEventTabStopEventTextEventValidatedEventValidatingEventVisibleEventVisibleChanged*/#endregion
View Code

 示例代码下载

.Net事件探索