转自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事件是委托类型,其中的委托类型又是怎么被实例化的呢?这些内容将在下一个专题给大家分享下的。

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

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

    引言: 前面几个专题对委托进行了详细的介绍的,然后我们在编写代码过程中经常会听到“事件”这个概念的,尤其是写UI的时候,当我们点击一个按钮后VS就会自动帮我们生成一些后台的代码,然后我们就只需要在Cl ...

  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. Java中int与Integer

    一般小写字母开头的是数据类型(如int double),大写字母开头的一般是封装为类(如Double),里面有很多方法,比如实行转换Integer.parseInt(arg0),可以把其他类型的数据转 ...

  2. WordPress Lazy SEO插件lazyseo.php脚本任意文件上传漏洞

    漏洞名称: WordPress Lazy SEO插件lazyseo.php脚本任意文件上传漏洞 CNNVD编号: CNNVD-201309-446 发布时间: 2013-09-26 更新时间: 201 ...

  3. MS SQL Server 如何得到执行最耗时的前N条T-SQL语句-

    --得到最耗时的前N条T-SQL语句 --适用于SQL SERVER 2005及其以上版本 --给N赋初值为30 ;with maco as ( select top (@n) plan_handle ...

  4. JavaScript浏览器本地数据存储

    浏览器本地存储主要使用的是sessionStorage和localStorage.两者都支持,sessionStorage保存的是浏览器和服务器的一次对话信息,只在一次回话中有效.当在新标签页或新窗口 ...

  5. E. Three States - Codeforces Round #327 (Div. 2) 590C States(广搜)

    题目大意:有一个M*N的矩阵,在这个矩阵里面有三个王国,编号分别是123,想知道这三个王国连接起来最少需要再修多少路. 分析:首先求出来每个王国到所有能够到达点至少需要修建多少路,然后枚举所有点求出来 ...

  6. Result

    1.常用四种类型: a)          dispatcher(默认) 服务器跳转(普通转发),就是forward到一个JSP或者HTML或者其他结果页面,不能是Action 视图请求地址是     ...

  7. 337. House Robber III

    二刷吧..不知道为什么house robbery系列我找不到笔记,不过印象中做了好几次了. 不是很难,用的post-order做bottom-up的运算. 对于一个Node来说,有2种情况,一种是选( ...

  8. apache启动目录(禁止目录)与设置默认入口文件的方法

    设置默认入口文件的方法: 打开apache的conf目录,找到httpd.conf文件,打开这个文件,搜索dir_module,找到以下截图修改位置进行修改,注意重启apache服务器,修改位置才会生 ...

  9. 是否以某字符串结尾 是否以某字符串开始 是否是整数 裁减字符串空格 是否是浮点数 是否所有字符为数字类型 是否为空 是否是EMAIL 是否是电话号码 身份证号码验证-支持新的带x身份证 日期验证

    /* 1.是否以某字符串结尾 endsWith(theStr,endStr) @param theStr:要判断的字符串 @param endStr:以此字符串结尾 @return boolean; ...

  10. 安装Win7和Ubuntu12.04双系统后,意外删除Ubuntu12.04引导文件,出现error:unknown filesystem;grub rescue>错误的解决方案

    很久之前在Win7基础上安装了Ubuntu12.04系统,采用硬盘安装的方法.分了1个10G的硬盘分区F盘用于存放Ubuntu12.04的引导文件,其实完全可以制作一个Ubuntu12.04的U盘启动 ...