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

转自http://www.cnblogs.com/zhili/archive/2012/10/27/Event.html

引言:

前面几个专题对委托进行了详细的介绍的,然后我们在编写代码过程中经常会听到“事件”这个概念的,尤其是写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事件是委托类型,其中的委托类型又是怎么被实例化的呢?这些内容将在下一个专题给大家分享下的。

时间: 2024-10-17 02:06:41

[C# 基础知识系列]专题四:事件揭秘的相关文章

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

转自http://www.cnblogs.com/zhili/archive/2012/10/22/Delegate.html 引言: 对于一些刚接触C# 不久的朋友可能会对C#中一些基本特性理解的不是很深,然而这些知识也是面试时面试官经常会问到的问题,所以我觉得有必要和一些接触C#不久的朋友分享下关于C#基础知识的文章,所以有了这个系列,希望通过这个系列让朋友对C#的基础知识理解能够更进一步.然而委托又是C#基础知识中比较重要的一点,基本上后面的特性都和委托有点关系,所以这里就和大家先说说委托

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

转自http://www.cnblogs.com/zhili/archive/2012/12/24/Linq.html 本专题概要: Linq是什么 使用Linq的好处在哪里 Linq的实际操作例子——使用Linq遍历文件目录 小结 引言: 终于到了C# 3中最重要特性的介绍了,可以说之前所有介绍的特性都是为了Linq而做准备的,然而要想深入理解Linq并不是这个专题可以介绍完的,所以我打算这个专题将对Linq做一个简单的介绍,对于Linq的深入理解我将会后面单独作为一个系列要和大家分享下. 一

[C#基础知识系列]专题十:全面解析可空类型[转]

原文链接 主要内容: 1:空合并操作符(?? 操作符) ??操作符也就是"空合并操作符",它代表的意思是两个操作数,如果左边的数不为null时,就返回左边的数,如果左边的数为null,就返回右边的数,这个操作符可以用于可空类型,也可以用于引用类型,但是不能用于值类型(之所以不能应用值类型(这里除了可空类型),因为??运算符要对左边的数与null进行比较,然而值类型,不能与null类型比较,所以就不支持??运算符),下面用一个例子来掩饰下??运算符的使用(??这个运算符可以方便我们设置默

线程基础知识系列(四)线程的同步2 线程通信和Condition变量

本文是系列的第四篇. 线程基础知识系列(三)线程的同步  :同步控制,锁及synchronized 线程基础知识系列(二)线程的管理 :线程的状态,控制,休眠,Interrupt,yield等 线程基础知识系列(一)线程的创建和启动  :线程的创建和启动,join(),daemon线程,Callable任务. 第三篇文章,重点阐述了如何使用锁和同步块对线程间共享可变变量保护,保证只有一个线程可以进入临界区.其实,没有过多的涉及另一个重要的同步概念:线程协作.第三篇中涉及的线程间并没有有效的协调.

[C# 网络编程系列]专题四:自定义Web浏览器

转自:http://www.cnblogs.com/zhili/archive/2012/08/24/WebBrowser.html 前言: 前一个专题介绍了自定义的Web服务器,然而向Web服务器发出请求的正是本专题要介绍的Web浏览器,本专题通过简单自定义一个Web浏览器来简单介绍浏览器的工作原理,以及帮助一些初学者揭开浏览器这层神秘的面纱(以前总感觉这些应用感觉很深奥的,没想到自己也可以自定义一个浏览器出来),下面不啰嗦了,进入正题. 一.Web浏览器的介绍 Web浏览器是指可以显示Web

线程基础知识系列(五)认识volatile

线程基础知识系列(四)线程的同步2  :线程的notify-wait通信机制,以及Condition条件变量 线程基础知识系列(三)线程的同步  :同步控制,锁及synchronized 线程基础知识系列(二)线程的管理 :线程的状态,控制,休眠,Interrupt,yield等 线程基础知识系列(一)线程的创建和启动  :线程的创建和启动,join(),daemon线程,Callable任务. 本篇文章主要讨论的关键字是volatile. volatile使用场景 volatile介绍 vol

C#基础知识篇(四)-----------C#笔记

一.类 1. 什么叫做类? 类是具有相同特征的一类事物统称.所以类是一种抽象,即不是一个实体(我们把类看做模板). 2. 什么叫做对象? 对象是根据类的模板创造出来的一个实体,它具有类里所有的特征,一个也多不得,一个也少不得.少了就不叫这个类的成员了,多了也不是!假如张三有变身这个功能,那么张三就不属于人. 记住对象是根据模板创建的,模板有什么它就有什么,不会多也不会少! 3. 什么叫做字段(或者是成员变量)? 我们把定义在方法的外面,类的里面(即:类中)的变量称之为字段或者说是成员变量. 4.

线程基础知识系列(三)线程的同步

本文是系列的第三篇,前面2篇,主要是针对单个线程如何管理,启动等,没有过多涉及多个线程是如何协同工作的. 线程基础知识系列(二)线程的管理 :线程的状态,控制,休眠,Interrupt,yield等 线程基础知识系列(一)线程的创建和启动  :线程的创建和启动,join(),daemon线程,Callable任务. 本文的主要内容 何谓线程安全? 何谓共享可变变量? 认识synchronized关键字 认识Lock synchronized vs Lock 1.何谓线程安全 多线程是把双刃剑,带

[SQL] SQL 基础知识梳理(四) - 数据更新

SQL 基础知识梳理(四) - 数据更新 [博主]反骨仔 [原文]http://www.cnblogs.com/liqingwen/p/5929786.html 目录 一.插入数据 1.INSERT 语句的基本语法 --语法: --INSERT INTO <表名>(列1, 列2, ...) VALUES (值1, 值2, ...) INSERT INTO dbo.Shohin ( shohin_id , shohin_mei , shohin_bunrui , hanbai_tanka , s