• C#委托和事件本质


    C#中委托和事件是很重要的组成部分,而掌握委托和事件的本质将必不可少。为了能探秘本质,写了如下代码

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading;
    using System.Threading.Tasks;
    
    namespace ConsoleApplication1
    {
        class Program
        {
            static void Main(string[] args)
            {
                Water water = new Water();
    
                water.WaterBoils += water_WaterBoils;
                water.WaterBoils += water_WaterBoils2;
    
                water.DegreeRise();
                Console.ReadLine();
            }
    
            static void water_WaterBoils(object sender, WaterBoilsEventArgs args)
            {
                Console.WriteLine(string.Format("sender:{0}", sender.ToString()));
                Console.WriteLine(string.Format("args:{0}", args.CurentDegree.ToString()));
            }
            public static void water_WaterBoils2(object sender, WaterBoilsEventArgs args)
            {
                Console.WriteLine(string.Format("sender_2:{0}", sender.ToString()));
                Console.WriteLine(string.Format("args_2:{0}", args.CurentDegree.ToString()));
            }
        }
    
        public delegate void WaterBoilsEventHandler(object sender,WaterBoilsEventArgs args);
    
        public class WaterBoilsEventArgs : EventArgs
        {
            public int CurentDegree { get; set; }
        }
    
        public class Water
        {
            public event WaterBoilsEventHandler WaterBoils;
            public int curentDegree = 0;
    
            public void DegreeRise()
            {
                for(var n = 99;n<200;n++)
                {
                    Thread.Sleep(1000);
    
                    if(n>=100)
                    {
                        if(WaterBoils!=null)
                        {
                            WaterBoils(this,new WaterBoilsEventArgs(){CurentDegree = n});
    
                            //WaterBoils.Invoke(this, new WaterBoilsEventArgs() { CurentDegree = n });
                        }
                    }
                }
            }
        }
    }

              介绍一下这段代码:定义了一个委托WaterBoilsEventHandler,一个类Water,Water的DegreeRise方法触发WaterBoils事件,一个事件参数WaterBoilsEventArgs ,一个Main方法.

              既然是要看委托了事件的本质,所以借助反编译工具(Reflector)查看编译后的代码片段:

    首先来看一下委托编译以后的代码:

    .class public auto ansi sealed WaterBoilsEventHandler
        extends [mscorlib]System.MulticastDelegate
    {
        .method public hidebysig specialname rtspecialname instance void .ctor(object 'object', native int 'method') runtime managed
        {
        }
    
        .method public hidebysig newslot virtual instance class [mscorlib]System.IAsyncResult BeginInvoke(object sender, 
    class ConsoleApplication1.WaterBoilsEventArgs args, class [mscorlib]System.AsyncCallback callback, object 'object') runtime managed
        {
        }
    
        .method public hidebysig newslot virtual instance void EndInvoke(class [mscorlib]System.IAsyncResult result) runtime managed
        {
        }
    
        .method public hidebysig newslot virtual instance void Invoke(object sender, class ConsoleApplication1.WaterBoilsEventArgs args)
     runtime managed
        {
        }
    
    }

    从反编译IL代码中可以看出:

             1.自定义委托的本质是一个类,具有Invoke,BeginInvoke,Endinvoke方法分别来支持同步和异步的执行,Invoke无返回值;BeginInvoke返回IAsyncResult,IAsyncResult可以作为EndInvoke的参数来结束执行。

             2.自定义委托继承自MulticastDelegate,使自定义委托具有“组播”的能力,也就是可以绑定多个委托;(MulticastDelegate类又继承自Delegate

    下面来看看“组播”是怎么实现的:MulticastDelegate内有两个方法CombineImpl和RemoveImpl分别完成委托的绑定和解绑定;内部有一个对象(使用中会转换成object[])_invocationList存储MulticastDelegate对象;还有其他的一些方法共同完成。

    从上边可以看出,委托是可以绑定执行多个方法的,那为什么还要事件呢,我觉得这个问题可能从语言设计角度上讲,委托的设计出发点是规避犹如在C、C++等中的指针变量,委托具把这个地址进行了包装,反编译Delegate类,发现有如下成员

    QQ截图20140712133307   _methodPtr:方法的指针,一个Delegate对象维护了一个方法的引用地址

    在使用委托的是否发现有有悖的地方:《 C# 与 .Net 3.5 高级程序设计第四版》有如下解释:

    “如果我们没有把委托成员变量定义为私有的,调用者就可以直接访问委托对象,这样调用者就可以把变量赋值为新的委托对象(实际上也就是删除了当前要调用的方法列表),更糟糕的是,调用者可以直接调用委托的调用列表。”

    这里就有一个比较严重的问题:1.比如两个对象去注册,A已经注册了一个方法,B去注册的时候把A注册的方法给删除了,并且B还可以操作到A注册的方法。这种不友好是不能忍受的。

    C#提供了event关键字来为我们解决问题,使用过event的童鞋们都可能有印象,在声明事件以外的其他类中,如果调用事件,只能干两件事情:

      1.绑定方法引用,

      2.解绑方法引用。

    这是对事件的限制。

    顺着这条思路,看看C#中事件是怎么完成的。

    首先看一看,Water类的反编译结果

    .class public auto ansi beforefieldinit Water
        extends [mscorlib]System.Object
    {
        .event ConsoleApplication1.WaterBoilsEventHandler WaterBoils
        {
            .addon instance void ConsoleApplication1.Water::add_WaterBoils(class ConsoleApplication1.WaterBoilsEventHandler)
            .removeon instance void ConsoleApplication1.Water::remove_WaterBoils(class ConsoleApplication1.WaterBoilsEventHandler)
        }
    
    
        .method public hidebysig specialname rtspecialname instance void .ctor() cil managed
        {
        }
    
        .method public hidebysig instance void DegreeRise() cil managed
        {
        }
    
    
        .field public int32 curentDegree
    
        .field private class ConsoleApplication1.WaterBoilsEventHandler WaterBoils
    
    }

    add_WaterBoils方法的方法定义如下:

    public void add_WaterBoils(WaterBoilsEventHandler value)
    {
        WaterBoilsEventHandler handler2;
        WaterBoilsEventHandler waterBoils = this.WaterBoils;
        do
        {
            handler2 = waterBoils;
            WaterBoilsEventHandler handler3 = (WaterBoilsEventHandler) Delegate.Combine(handler2, value);
            waterBoils = Interlocked.CompareExchange<WaterBoilsEventHandler>(ref this.WaterBoils, handler3, handler2);
        }
        while (waterBoils != handler2);
    }

    remove_WaterBoils方法的方法定义如下

    public void remove_WaterBoils(WaterBoilsEventHandler value)
    {
        WaterBoilsEventHandler handler2;
        WaterBoilsEventHandler waterBoils = this.WaterBoils;
        do
        {
            handler2 = waterBoils;
            WaterBoilsEventHandler handler3 = (WaterBoilsEventHandler) Delegate.Remove(handler2, value);
            waterBoils = Interlocked.CompareExchange<WaterBoilsEventHandler>(ref this.WaterBoils, handler3, handler2);
        }
        while (waterBoils != handler2);
    }

    可以看出:

    1.增加了两个方法:add_WaterBoils 和 remove_WaterBoils;

    2.还增加了一个WaterBoilsEventHandler 类型的 私有变量 WaterBoils且与声明的事件对象名相同

    这里容易产生一点迷惑:.event ConsoleApplication1.WaterBoilsEventHandler WaterBoils 有点像一个内部类的结构,且这个“类”具有两个方法。暂时把这个迷惑放下。

    3.add_WaterBoils和remove_WaterBoils方法都是对私有字段WaterBoils的维护。

    知道这些后,再开看看注册的时候是怎么调用的:

    private static void Main(string[] args)
    {
        Water water = new Water();
        water.WaterBoils += new WaterBoilsEventHandler(Program.water_WaterBoils);
        water.WaterBoils += new WaterBoilsEventHandler(Program.water_WaterBoils2);
        water.DegreeRise();
        Console.ReadLine();
    }

    这是使用的C#代码方式,看不出来什么结果,查看IL代码如下:

    .method private hidebysig static void Main(string[] args) cil managed
    {
        .entrypoint
        .maxstack 3
        .locals init (
            [0] class ConsoleApplication1.Water water)
        L_0000: nop 
        L_0001: newobj instance void ConsoleApplication1.Water::.ctor()
        L_0006: stloc.0 
        L_0007: ldloc.0 
        L_0008: ldnull 
        L_0009: ldftn void ConsoleApplication1.Program::water_WaterBoils(object, class ConsoleApplication1.WaterBoilsEventArgs)
        L_000f: newobj instance void ConsoleApplication1.WaterBoilsEventHandler::.ctor(object, native int)
        L_0014: callvirt instance void ConsoleApplication1.Water::add_WaterBoils(class ConsoleApplication1.WaterBoilsEventHandler)
        L_0019: nop 
        L_001a: ldloc.0 
        L_001b: ldnull 
        L_001c: ldftn void ConsoleApplication1.Program::water_WaterBoils2(object, class ConsoleApplication1.WaterBoilsEventArgs)
        L_0022: newobj instance void ConsoleApplication1.WaterBoilsEventHandler::.ctor(object, native int)
        L_0027: callvirt instance void ConsoleApplication1.Water::add_WaterBoils(class ConsoleApplication1.WaterBoilsEventHandler)
        L_002c: nop 
        L_002d: ldloc.0 
        L_002e: callvirt instance void ConsoleApplication1.Water::DegreeRise()
        L_0033: nop 
        L_0034: call string [mscorlib]System.Console::ReadLine()
        L_0039: pop 
        L_003a: ret 
    }

    重点分析一下 L_0009 到 L_0014 的这三行代码:

    L_0009 :把water_WaterBoils方法的指针推送到计算堆栈上

    L_000f  :创建一个WaterBoilsEventHandler委托对象

    L_0014 :调用add_WaterBoils,并把结果推送到计算堆栈上

    同时请注意:“ConsoleApplication1.Water::add_WaterBoils”这句代码,add_WaterBoils是ConsoleApplication1.Water类对象的方法,上边的迷惑,看着像一个“内部类”,实则没那关系。

    这段代码让我们明白:事件的注册是调用编译生成的方法 add_WaterBoils 把 委托中带有的方法引用地址 维护到编译生成的私有属性 WaterBoils 中去了。

    所以event就是语法糖,节省了编写代码的时间,并且事件的触发使用专门的事件来处理,显出语言本身的完整,真正的实现是编译器生成委托对象和其他处理代码实现的

    最近面试,遇到一个问题:“事件是不是一种委托?”

    来看一下事件对象是什么

    在快速监视器中查看:

    可以看出,申明的事件对象( water.WaterBoils) 是委托(WaterBoilsEventHandler)实例。

    “ .event ConsoleApplication1.WaterBoilsEventHandler WaterBoils”中的 “.event”  则表示 “WaterBoils”具有特殊性,是事件。

    我的理解:

      C#中的事件就是一种语法糖,不是一些人认为的是对委托的封装,它是事件处理的一种指导性应用方式,event使其具有一定的约束性,带有“最小化影响”的设计原则(不允许影响他人的注册),它是依靠委托来实现的,但它不是一种委托,它是比委托更抽象的一种应用方式。

      而事件对象本身就是委托实例(CLR可能在运行的时候会把事件对象与相同名称的私有field进行关联,比如调试的时候可以看出,事件对象的值就是该相同名称的私有field的值)。

    欢迎大家谈谈自己的看法 

    希望与大家多多交流  QQ:373934650;群Q:227231436

    如果有用请大伙儿帮忙推荐一下,谢谢!

  • 相关阅读:
    计算机的时空观以及俩者间的相互转换
    发送一个记录数据包
    流的压缩与解压缩函数
    通过自定义消息调用主窗体的菜单项
    多标签窗体的释放一法
    记录数组存出到文件和从文件导入
    变体记录
    内存流的使用
    用流读写结构化文件
    下拉框、下拉控件之Select2
  • 原文地址:https://www.cnblogs.com/beixing/p/3839952.html
Copyright © 2020-2023  润新知