引言:

前面几个专题对委托进行了详细的介绍的,然后我们在编写代码过程中经常会听到“事件”这个概念的,尤其是写UI的时候,当我们点击一个按钮后VS就会自动帮我们生成一些后台的代码,然后我们就只需要在Click方法里面写代码就可以,所以可能有些刚接触C#的朋友就觉得这样很理所当然的,也没有去思考这是为什么的,为什么点击下事件就会触发我们在Click方法里面写的代码呢?事件到底扮演个什么样的角色呢?为了解除大家的这些疑惑,下面就详细介绍了事件,让一些初学者深入理解C#中的事件的概念。

一、为什么C#中会有事件的?

  前面专题中介绍了我理解的为什么需要委托的,所以这里我来分享下我理解的为什么C#中要引入事件这个概念的。下面就简单讲讲生活中事件的例子的,最近我生日刚过完的,我就以生日这个话题要谈谈的,日子一天天的过去,当生日的日期到的时候,这时候就触发了生日事件的,此时过生日的人就是触发生日事件的对象的,然后有些关系你的朋友就会对这个事件进行关注,一旦这个事件触发, 他们就可能会陪你一起庆祝生日,然后送礼物给你,当然并不是所有人都会对你的生日关注的,有些人肯定根本就不知道的, 只有对于你生日事件进行了关注了的人才会送礼物给你。这样的生活中的一个生日过程,然而对于为什么C#中会有事件这个概念当然就更好理解了,C#是一个面向对象的语言,我们使用C#语言进行编码也是为了用代码帮助我们完成现实生活中的事情的,所以当然也就必须有事件来反映生活中发生事情的情况了。

二、自己如何实现一个事件模式的?

  现在我们知道了为什么C#要引入事件了,但是对于我们在代码中使用的事件大部分都是.net类库为我们提供的,例如控件的各种事件,我们只需要点击按钮后就会触发点击事件的,但是我们很想理解这个事件是如何触发的,我们是否可以自己定义实现事件模式的一个程序的呢?答案当然是可以的,下面就以上面生日的例子来通过代码来解释下如何实现一个事件模式的。

具体代码为:

using System;
using System.Threading;

namespace BirthdayEventDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            // 实例化一个事件源对象
            Me eventSource = new Me("Learning Hard");

            // 实例化关注事件的对象
            Friend1 obj1 = new Friend1();
            Friend2 obj2 = new Friend2();

            // 使用委托把对象及其方法注册到事件中
            eventSource.BirthDayEvent+=new BirthDayEventHandle(obj1.SendGift);
            eventSource.BirthDayEvent+=new BirthDayEventHandle(obj2.Buycake);

            // 事件到了触发生日事件,事件的调用
            eventSource.TimeUp();
            Console.Read();
        }
    }
    // 第一步: 定义一个类型用来保存所有需要发送给事件接收者的附加信息
    public class BirthdayEventArgs : EventArgs
    {
        // 表示过生日人的姓名
        private readonly string name;

        public string Name
        {
            get { return name;}
        }

        public BirthdayEventArgs(string name)
        {
            this.name = name;
        }
    }
   // 第二步:定义一个生日事件,首先需要定义一个委托类型,用于指定事件触发时被调用的方法类型
    public delegate void BirthDayEventHandle(object sender, BirthdayEventArgs e);
    // 定义事件成员
    public class Subject
    {
        // 定义生日事件
        public event BirthDayEventHandle BirthDayEvent;

        // 第三步:定义一个负责引发事件的方法,它通知已关注的对象(通知我的好友)
        protected virtual void Notify(BirthdayEventArgs e)
        {
            // 出于线程安全的考虑,现在将对委托字段的引用复制到一个临时字段中
            BirthDayEventHandle temp = Interlocked.CompareExchange(ref BirthDayEvent, null, null);
            if (temp != null)
            {
                // 触发事件,与方法的使用方式相同
                // 事件通知委托对象,委托对象调用封装的方法
                temp(this, e);
            }
        }
    }

    // 定义触发事件的对象,事件源
    public class Me : Subject
    {
        private string name;
        public Me(string name)
        {
            this.name = name;
        }
        public void TimeUp()
        {
            BirthdayEventArgs eventarg = new BirthdayEventArgs(name);
            // 生日到了,通知朋友们
            this.Notify(eventarg);

        }
    }

    // 好友对象
    public class Friend1
    {
        public void SendGift(object sender,BirthdayEventArgs e)
        {
            Console.WriteLine(e.Name+" 生日到了,我要送礼物");
        }
    }
    public class Friend2
    {
        public void Buycake(object sender, BirthdayEventArgs e)
        {
            Console.WriteLine(e.Name + " 生日到了,我要准备买蛋糕");
        }
    }
}

运行结果为:

三、编译器是如何解释事件的呢?

  上面我们已经介绍了如何去实现自己去实现一个事件模式的,大家可以展开代码来具体的查看的,实现过程主要是——定义触发对象的事件源(指的是谁过生日)->定义关注你生日事件的朋友对象-> 方法登记对事件的关注,当事件触发时通知登记的方法被调用。然而相信大家还有有疑问——到底C#中的事件是什么呢?编译器又是如何去解释它的?下面就为大家解除下疑惑的:

  首先事件其实就是委托的(确切的说事件就是委托链),从上面的代码中,我们定义的事件除了使用event关键字外,还用到了一个委托类型,然而委托是一个类,类肯定就有属性字段的,然而我们就可以把事件理解为委托的一个属性,属性的返回值是一个委托类型。说事件是委托的一个属性,是有根据的,我们通过中间语言代码可以知道编译器是如何去解释我们定义的事件的。

 // 第二步:定义一个生日事件,首先需要定义一个委托类型,用于指定事件触发时被调用的方法类型
    public delegate void BirthDayEventHandle(object sender, BirthdayEventArgs e);
    // 定义生日事件
        public event BirthDayEventHandle BirthDayEvent;

当我们像上面定义一个事件时,编译器会把它转换为3段代码(大家可以通过IL反汇编程序来查看的):

     // 1. 一个被初始化为null的私有委托字段
       private BirthDayEventHandle BirthDayEvent =null;

        //2. 一个公共add_BirthDayEvent方法
        public void add_BirthDayEvent(BirthDayEventHandle value)
        {
            // 以一种线程安全的方式从事件中添加一个委托
        }
        // 3. 一个公共的remove_BirthDayEvent方法
        public void remove_BirthDayEvent(BirthDayEventHandle value)
        {
            // 以一种线程安全的方式从事件中移除一个委托
        }

第一段代码一个委托的私有字段,该字段是对一个委托列表的头部的引用,事件发生时会通知这个列表中的委托。字段初始化为null,表明无关注人登记了对事件的关注。
第二段代码是一个以add为前缀的方法,该方法是由编译器自动命名的,代码内容调用Delegate.Combine方法将委托实例添加到委托列表中,返回新的列表地址,并将这个地址存回字段。

第三段代码也是一个方法,它使得一个对象注销对事件的关注,同样的方法体调用Delegate.Remove方法将委托实例从委托列表中删除,返回新的列表地址,并将这个地址存回字段中。(注,如果试图删除一个从未添加过的方法,Delegate.Remove方法在内部将不做任何事情,也就是说,不会抛出任何一次,也不会显示任何警告,事件的方法集合保持不变)。

同时大家也可以通过调试来说明事件是一个委托链的,大家可以在 eventSource.BirthDayEvent+=new BirthDayEventHandle(obj2.Buycake);这行代码设置一个断点调试的,下面是我调试过程中的一个截图,大家也可以自己调试看看的,这样将会更加理解事件是一个委托链的概念:

按F10运行一行后的截图

通过上面的截图,相信大家对于事件是一个委托链的概念相信会有进一步的理解的。

四、小结

  到这里本专题的内容也就介绍完了, 希望通过本专题,大家可以对事件有进一步的理解,理解事件与委托之间的关系。这个专题通过自己实现的一个事件模式里解释事件的本质,然而我们经常使用的是Net类库中定义好的事件,然而有些刚接触C#的人却不理解Net中定义的事件背后所做的事情,只是知道点下按钮后在Click方法里面写入自己的一些控制代码,然而背后的过程具体是怎样的,既然事件是委托,那么Click事件是委托类型,其中的委托类型又是怎么被实例化的呢?这些内容将在下一个专题给大家分享下的。

[C# 基础知识系列]专题四:事件揭秘 (转载)的更多相关文章

  1. [C# 基础知识系列]专题四:事件揭秘

    转自http://www.cnblogs.com/zhili/archive/2012/10/27/Event.html 引言: 前面几个专题对委托进行了详细的介绍的,然后我们在编写代码过程中经常会听 ...

  2. [C# 基础知识系列]专题一:深入解析委托——C#中为什么要引入委托

    转自http://www.cnblogs.com/zhili/archive/2012/10/22/Delegate.html 引言: 对于一些刚接触C# 不久的朋友可能会对C#中一些基本特性理解的不 ...

  3. [C# 基础知识系列]专题九: 深入理解泛型可变性

    引言: 在C# 2.0中泛型并不支持可变性的(可变性指的就是协变性和逆变性),我们知道在面向对象的继承中就具有可变性,当方法声明返回类型为Stream,我们可以在实现中返回一个FileStream的类 ...

  4. [C# 基础知识系列]专题五:当点击按钮时触发Click事件背后发生的事情 (转载)

    当我们在点击窗口中的Button控件VS会帮我们自动生成一些代码,我们只需要在Click方法中写一些自己的代码就可以实现触发Click事件后我们Click方法中代码就会执行,然而我一直有一个疑问的—— ...

  5. [C# 基础知识系列]专题七: 泛型深入理解(一) (转载)

    引言: 在上一个专题中介绍了C#2.0 中引入泛型的原因以及有了泛型后所带来的好处,然而上一专题相当于是介绍了泛型的一些基本知识的,对于泛型的性能为什么会比非泛型的性能高却没有给出理由,所以在这个专题 ...

  6. 方法字段[C# 基础知识系列]专题二:委托的本质论

    首先声明,我是一个菜鸟.一下文章中出现技术误导情况盖不负责 引言: 上一个专题已和大家分享了我懂得的——C#中为什么须要委托,专题中简略介绍了下委托是什么以及委托简略的应用的,在这个专题中将对委托做进 ...

  7. [C# 基础知识系列]专题十六:Linq介绍

    转自http://www.cnblogs.com/zhili/archive/2012/12/24/Linq.html 本专题概要: Linq是什么 使用Linq的好处在哪里 Linq的实际操作例子— ...

  8. [C# 基础知识系列]专题三:如何用委托包装多个方法——委托链 (转载)

    引言: 上一专题介绍了下编译器是如何来翻译委托的,从中间语言的角度去看委托,希望可以帮助大家进一步的理解委托,然而之前的介绍都是委托只是封装一个方法,那委托能不能封装多个方法呢?因为生活中经常会听到, ...

  9. [C# 基础知识系列]专题二:委托的本质论 (转载)

    引言: 上一个专题已经和大家分享了我理解的——C#中为什么需要委托,专题中简单介绍了下委托是什么以及委托简单的应用的,在这个专题中将对委托做进一步的介绍的,本专题主要对委本质和委托链进行讨论. 一.委 ...

随机推荐

  1. idea 的一些基本的配置

    1. 2. 3. http://139.199.89.239:1008/88414687-3b91-4286-89ba-2dc813b107ce 531403098@qq.com R+0oOAb3rx ...

  2. 调试 kafka manager 源码

    前提:可以上外网. kafka manager 是一款优秀的监控 kafka 的工具,采用 scala 语言编写,如何调试 kafka manager 呢? kafka manager 使用 play ...

  3. python数据结构与算法之问题求解

    懂得计算机的童鞋应该都知道,一条计算机程序由数据结构跟算法两大部分组成.所以,其实不管你使用哪种计算机语言编写程序,最终这两部分才是一个程序设计的核心.所以,一个不懂得数据结构与算法的程序员不是一个好 ...

  4. 安装vue-cli时-4058报错的解决方法

    一.报错信息 安装vue-cli时-4058报错 二.解决办法 1.安装淘宝镜像 npm --registry https://registry.npm.taobao.org info undersc ...

  5. socket应用(vue、node.js、M站)

    socket应用(vue.node.js.M站) 前言:我们在做一些项目的时候需要做到实时变化, 比如我们有时候有需求会要求我们做一个类似于聊天室的页面 比如有些时候我们对某些东西进行点赞和刷票,需要 ...

  6. 开发企业微信打卡API笔记

    获取企业微信打开API上面的数据 根据企业ID和打卡模块的secret获取access_token 打卡传参body为json格式的字符传 创建打卡对象把参数写入,useridlist为list格式. ...

  7. go报错unimplemented: 64-bit mode not compiled in与mingw 64位安装报错ERROR res已解决

    问题一:cc1.exe: sorry, unimplemented: 64-bit mode not compiled in 参考:https://www.cnblogs.com/lesroad/p/ ...

  8. 异常java.lang.NumberFormatException解决

    原因一:超出了int类型的取值范围 项目中要把十六进制字符串转化为十进制, 用到了到了Integer.parseInt(str1.trim(), 16):这个是不是后抛出java.lang.Numbe ...

  9. JS 函数参数 及 函数数组

    <script> function a(){ alert("a"); } function b(){ alert("b"); } var arr = ...

  10. vBox Arch UEFI LVM安装

    Table of Contents 介绍 配置 基础 VirtualBox配置 安装准备 基础 分区 格式化 挂载 安装 选择镜像 安装基本系统 配置 fstab chroot 一些配置 lvm2 网 ...