首页 > 代码库 > .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反编译生成的文件,反编译后发现编译器为我们做了不少工作,主要有两个方面:
- 为事件增加了私有的、和事件名称相同的字段
- 为事件增加了 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
示例代码下载
.Net事件探索