.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事件索引关键字字段名称

/*
EventAutoSizeChanged
EventBackColor
EventBackgroundImage
EventBackgroundImageLayout
EventBindingContext
EventCausesValidation
EventChangeUICues
EventClick
EventClientSize
EventContextMenu
EventContextMenuStrip
EventControlAdded
EventControlRemoved
EventCursor
EventDock
EventDoubleClick
EventDragDrop
EventDragEnter
EventDragLeave
EventDragOver
EventEnabled
EventEnabledChanged
EventEnter
EventFont
EventForeColor
EventGiveFeedback
EventGotFocus
EventHandleCreated
EventHandleDestroyed
EventHelpRequested
EventImeModeChanged
EventInvalidated
EventKeyDown
EventKeyPress
EventKeyUp
EventLayout
EventLeave
EventLocation
EventLostFocus
EventMarginChanged
EventMouseCaptureChanged
EventMouseClick
EventMouseDoubleClick
EventMouseDown
EventMouseEnter
EventMouseHover
EventMouseLeave
EventMouseMove
EventMouseUp
EventMouseWheel
EventMove
EventPaddingChanged
EventPaint
EventParent
EventPreviewKeyDown
EventQueryAccessibilityHelp
EventQueryContinueDrag
EventRegionChanged
EventResize
EventRightToLeft
EventSize
EventStyleChanged
EventSystemColorsChanged
EventTabIndex
EventTabStop
EventText
EventValidated
EventValidating
EventVisible
EventVisibleChanged
*/

#endregion
View Code

 示例代码下载

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。