看看这个常常被初级程序员弄不懂的 “事件”

简介:

       众所周知在面试中,经常有些崽子面试官会问些“事件和委托”的关系,也许一路走来的程序员大多都会被问到这个,那么对于这个

高频的”事件和委托“问题,如何回击呢?首先我从最经典的一套面试题说起,用事件来实现 “猫爪老鼠“,这是一个从网上copy过来的一

个例子。

static void Main(string[] args)
        {
            Mouse mouse = new Mouse();

            Cat cat = new Cat();

            cat.OnCry();

            Console.ReadLine();
        }
    }

    public delegate void CryEventHandler();

    public class Cat
    {
        public static event CryEventHandler Cry;

        public Cat()
        {
            Console.WriteLine("Cat:I'm coming.");
        }

        public virtual void OnCry()
        {
            Console.WriteLine("Cat:MiaoMiao");
            if (Cry != null)
            {
                Cry.Invoke();
            }
        }
    }

    public class Mouse
    {
        public Mouse()
        {
            Cat.Cry += new CryEventHandler(Run);
            Console.WriteLine("Mouse:I go to find something,and I must always listen cat's crying.");
        }

        public void Run()
        {
            Console.WriteLine("Mouse:A cat is coming,I must go back!");
        }
    }

 事件定义啥的什么玩意这个我就不说了,没什么意思,为了了解这个跟委托有什么关系,下面我们来看看这段代码最后生成的IL是什么样的。

 

1:CryEventHandler委托

1 public delegate void CryEventHandler();

   这个我想大家都清楚,委托本质上是一个继承于MulticastDelegate的类,同时会生成仅有的4个方法,看下IL即知。

 

2:Cat类

public class Cat
    {
        public static event CryEventHandler Cry;

        public Cat()
        {
            Console.WriteLine("Cat:I'm coming.");
        }

        public virtual void OnCry()
        {
            Console.WriteLine("Cat:MiaoMiao");
            if (Cry != null)
            {
                Cry.Invoke();
            }
        }
    }

从这个类中,我们看到了一个Cry事件,然后就是一个Cry.Invoke(),不过当你看到Invoke的时候,你是不是很怀疑Cry是不是一个委托字段呢?

其实你怀疑的是一点问题都没有,32个赞,看下IL。

 

从上图中我们看到了两个好玩的东西:

① field Cry 字段,完整定义如下,然来所谓的“事件字段” 其实在IL下面蜕变成了委托字段,如果你觉得很奇怪,请看第二点。

.field private static class Sample.CryEventHandler Cry

② add_Cry,remove_Cry,如果仅仅将事件字段变成委托字段,那确实是编译器在发什么神经,然来编译器还给事件配备了两个方法,这个

    其实也就是事件里面+=,-=的奥秘所在,下面我们挑add_Cry方法说下,看看方法定义的IL代码是怎么样的。

 

很新奇,我们找到了Combine方法,这个我们都知道,原来事件中的+=,其实就是利用Combine来将当前的委托实例放到Delegate的

委托链表中(其实里面是array实现的),为了方便理解,我把上面的IL代码翻译成C#代码。

public class Cat
    {
        /// <summary>
        /// 私有的委托变量
        /// </summary>
        private static CryEventHandler Cry;

        /// <summary>
        /// 事件生成的方法
        /// </summary>
        /// <param name="cryEventHandler"></param>
        public void Add_Cry(CryEventHandler cryEventHandler)
        {
            var result = (CryEventHandler)Delegate.Combine(Cry, cryEventHandler);

            Interlocked.CompareExchange<CryEventHandler>(ref Cry, result, Cry);
        }

        public void Remove_Cry(CryEventHandler cryEventHandler)
        {
            var result = (CryEventHandler)Delegate.Remove(Cry, cryEventHandler);

            Interlocked.CompareExchange<CryEventHandler>(ref Cry, result, Cry);
        }

        public Cat()
        {
            Console.WriteLine("Cat:I'm coming.");
        }

        public virtual void OnCry()
        {
            Console.WriteLine("Cat:MiaoMiao");

            if (Cry != null)
            {
                //委托专用的四个方法,invoke,begininvoke,endinvoke,ctor
                Cry.Invoke();
            }
        }
    }

可能有些同学对IL指令不是很熟悉,没关系,我也一样,咱博客园上面有位大神飞鸟的一篇IL指令集的博文或许能帮得到你。

 

3:Mouse类

    如果你对Cat类的IL代码琢磨的差不多的话,下面这个Mouse类就非常简单了,仅仅调用而已嘛。

public class Mouse
    {
        public Mouse()
        {
            Cat.Cry += new CryEventHandler(Run);
            Console.WriteLine("Mouse:I go to find something,and I must always listen cat's crying.");
        }

        public void Run()
        {
            Console.WriteLine("Mouse:A cat is coming,I must go back!");
        }
    }

这个地方最让人关心的就是:Cat.Cry += new CryEventHandler(Run) 这个语句,从它的IL中可以看到其实做了三件事。

① ldftn:       将Run方法的非托管指针推送到计算堆栈上。

② newobj:   将CryEventHandler委托new一下,同时将计算堆栈上的Run方法的非托管指针作为构造函数的参数。

③ call:         调用Cat类的Add_Cry方法,将CryEventHandler的实例作为参数传递下去。

 

下面继续将该IL代码反编译回来,不过针对IL指令:call       void Sample.Cat::add_Cry(class Sample.CryEventHandler)

并没有很好的翻译过来,只能new Cat()了一下才能调用Add_Cry,从而触发了Cat的构造函数。

public class Mouse
    {
        public Mouse()
        {
            var cryHandler = new CryEventHandler(Run);

            /*
             *  针对IL:call       void Sample.Cat::add_Cry(class Sample.CryEventHandler)
             *  这个没有反编译好,因为我new Cat()将会再次调用构造函数。
             */
            new Cat().Add_Cry(cryHandler);

            Console.WriteLine("Mouse:I go to find something,and I must always listen cat's crying.");
        }

        public void Run()
        {
            Console.WriteLine("Mouse:A cat is coming,I must go back!");
        }
    }

 

好了,说了这么多,应该也有总结性的东西出来了,原来事件是完完全全的建立在委托的基础上,你可以认为事件就是用委托来玩一个

观察者模式的,你甚至可以认为事件就是委托。没有本质区别。

相关文章
给予第一次接触编程的朋友
给予第一次接触编程的朋友
编程要搞明白的东西(一)
编程要搞明白的东西(一)
87 0
|
7月前
|
前端开发 Java 数据库连接
如何顺利完成毕业项目看完这篇文章有你想要的!
如何顺利完成毕业项目看完这篇文章有你想要的!
|
前端开发 JavaScript Java
前端小白的几个坏习惯
前端小白的几个坏习惯
100 1
|
Java
编程要搞明白的东西(二)
编程要搞明白的东西(二)
89 0
|
测试技术
初级软件测试面试题怎么找?提供的这两个地方你肯定用得上
最近几年,随着电子产品和互联网的蓬勃发展,各类科技公司如雨后春笋般出现,而软件公司作为科技类公司中的重要组成部分,在这支互联网大军中也占据了重要一席。因而,负责软件问题质检的软件测试岗位也逐渐成了这几年炙手可热的就业岗位之一。
151 0
|
机器学习/深度学习 前端开发 小程序
想当程序员,如何判断自己是否适合当前端程序员?
真正学习的时候,大多数所谓的兴趣就是扯淡; 只有那种遇到难题就兴奋,敲代码时候,越挫越勇的人,才叫感兴趣; 真正的感兴趣是让你学的久,学的不累;
180 0
|
小程序 前端开发 程序员
讨老婆开心,阿里云程序员的方式,竟然是…
阿里坚持每年都举办集体婚礼,还得从“阿里日”开始说起。 2003年“非典”期间,因一位员工被确诊“疑似”,全体阿里员工必须搬着电脑回家隔离办公。为纪念那段艰苦岁月中的坚韧坚持,感恩家人的温暖支撑,2005年起,阿里决定将每年的5月10日设立为“阿里日”,并从2006年起,每年举办员工集体婚礼。 当阿里集体婚越来越受到阿里人追捧时, “公益”成为集体婚礼另一个重要的关键词。每年累计公益时长最高的两对夫妻,可以免于抽签,直接参加集体婚礼。 为了给妻子王若芸一个别致却更有意义的婚礼,这一年,阿里云程序员驿桥积累了599个公益时,这是他和妻子的爱情故事。
412 0
讨老婆开心,阿里云程序员的方式,竟然是…
|
存储 安全 搜索推荐
天选程序员:如何提个好问题?
天选程序员:如何提个好问题?
275 0
天选程序员:如何提个好问题?
|
SpringCloudAlibaba 架构师 程序员
为什么说程序员不能只会安静写代码?| 开发者必读(013期)
最炫的技术新知、最热门的大咖公开课、最有趣的开发者活动、最实用的工具干货,就在《开发者必读》!
803 0

相关实验场景

更多