C#基础原理拾遗——面试都爱问的委托和事件(纠正)
这篇博客是我昨天写的,文中的观点有些问题,后经过网友留言和个人学习发现错误,原文还是保留,更改补在后面,不怕贻笑大方,唯恐误人子弟。不知道还能不能放在首页,让被误导的同学再被反误导一次。
一、原文
几乎所有的面试题都会问:事件是委托吗,说说委托和事件的联系和区别?每次答这个题都很蛋疼,因为把它们的关系说简单了就描述不准确,想说清楚就不是一两句话的事了。我通常在回答中加这么一句:委托与事件的关系好比字段与属性的关系。很多人理解它们的关系时也做这样的类比,虽然简单一句话概括了它们的关系,但总不能让我感到满意。
1、委托与事件到底什么关系?
当我们谈委托与事件的关系时,是说委托这种类型和事件这种类型的关系呢,还是具体的委托对象和事件对象之间的关系?我以为是前者。那么委托和事件是两种类型,而字段和属性是具体的对象,虽然都是封装,我觉得两者之间还是有区别的,事件对委托的封装是在类级别的、抽象层次、稳定的封装,而属性对字段的封装是在对象级别的、具体的、可自定义的封装。(个人理解,后来证明有偏差)由此造成的最直观的区别就是,在发布者类中使用事件时,不需要提供对应的委托对象;而在类中使用属性时,一般要提供对应的字段让属性来进行封装。
Reflector查看类之间的继承关系如下:

MSDN中有这么一句:“事件是特殊类型的多路广播委托,仅可从声明它们的类或结构(发行者类)中调用。”如此看来事件与委托的关系应该是继承关系,在继承的过程中在EventHandler类中进行的封装。
以上是从理论的角度分析了事件和委托的关系,下面通过代码加强认识。
2、委托模拟事件
实现一个非常简单的功能:控制台输入一个数,如果输入100的话就显示”Game Over”。用事件和委托模拟的事件分别实现该功能。
class Program
{
static void Main(string[] args)
{
//事件实现
Game1 game = new Game1();
game.GameOverEvent += game_GameOver; ////委托实现
//Game2 game = new Game2();
//game.AddMethod(game_GameOver); while (true)
{
Console.WriteLine("输入:"); game.Scroe = Convert.ToInt32(Console.ReadLine());
}
} static void game_GameOver(object sender, EventArgs e)
{
Console.WriteLine("Game Over");
} static void game_GameOver()
{
Console.WriteLine("Game Over");
}
} //事件实现
class Game1
{
//event 关键字用于在发行者类中声明事件。
public event EventHandler GameOverEvent; private int scroe; public int Scroe
{
get { return scroe; }
set
{
this.scroe = value;
if (this.GameOverEvent != null)
{
if (value == )
{
this.GameOverEvent(this, new EventArgs());//触发事件
}
}
}
}
} //委托实现
class Game2
{
public delegate void OverDelegate(); //将委托声明为private,防止订阅者直接调用,使用new等功能
private OverDelegate GameOverDelegate; //AddMethod和RemoveMethod模拟事件的+=和-=赋值
public void AddMethod(OverDelegate over)
{
this.GameOverDelegate += over;
} public void RemoveMethod(OverDelegate over)
{
this.GameOverDelegate -= over;
} private int scroe; public int Scroe
{
get { return scroe; }
set
{
this.scroe = value;
if (this.GameOverDelegate != null)
{
if (value == )
{
this.GameOverDelegate();//触发委托
}
}
}
}
}
以上Game2类中,委托对事件的模拟即是在对象级别做的封装,AddMethod和RemoveMethod方法是在Game2类中实现的,而不是在OverDelegate委托中实现的,因此事件的模拟依赖了OverDelegate和Game2两个类。而Game1中EventHandler类本身就封装了委托,限制了其在外部(订阅者)的实例化,这种功能的实现没有依赖于Game1。所以事件的这种内部封装机制减少了依赖,符合松耦合要求。
以上是我个人的一点理解,有失偏颇之处还望批评指正。
然而EventHandler内部是怎么封装的呢?我的思路尚不清晰,Reflector查看也没看出个所以然,我自己会认真学习探索,在此也向各位请教,希望大家能告诉我答案或提供一些思路。为谢!
二、改正
3、其实不是EventHandler一人的功劳
在上文中我以为事件对委托的封装是EventHandler类一人的功劳,其实没有深入理解的话很容易这么想,因为在声明事件的时候并没有声明对应的委托,直观上就感觉这个封装是发生在EventHandler类内部的。看了 @小AI 给我推荐的http://www.cnblogs.com/JimmyZhang/archive/2007/09/23/903360.html一文,发现问题所在。
在Reflector中反编译上面的例子,Game1类的结构如下:

Game1在此就是发布者类,对事件GameOverEvent的封装还是在发布者类中完成的,并不是在EventHandler类中就已经完成。这种封装模式和属性对字段的封装模式是完全一样的,只不过这个封装过程是由于有了event关键字在编译阶段由编译器自动完成的,在发布者类中将EventHandler类型的GameOverEvent委托设为私有,并加上Add和Remove方法,和上面用委托模拟的事件是一样的,也就是事件的本质就是在小标题2中用委托模拟的事件。这么说来,事件对委托的封装是可以类比为属性对字段的封装的,如果说有区别,那就是前者是编译阶段完成的、不可自定义的封装,后者是coding阶段实现的、可自定义的封装,但它们都是在对象级别完成的。
虽然这次出了错,但并没有使我灰心,相反,如果我不把自己的想法拿出来和大家交流,可能问题永远得不到解决。唯一的弊端就是大胆的写自己的想法,如果是错误的话,很可能误导比我还菜的同学,我能做的就是尽量多方求证,并且出错后及时改正。也勉励和我一样的菜鸟多写博客,别怕暴漏错误,大家对于大牛中规中矩的blog已经看的boring了,小菜错误的想法里可能偶尔就有着不被规则约束的创新。hehe
C#基础原理拾遗——面试都爱问的委托和事件(纠正)的更多相关文章
- C#基础原理拾遗——引用类型的值传递和引用传递
C#基础原理拾遗——引用类型的值传递和引用传递 以前写博客不深动,只搭个架子,像做笔记,没有自己的思考,也没什么人来看.这个毛病得改,就从这一篇开始… 最近准备面试,深感基础之重要,奈何我不是计算机科 ...
- 应届生/社招面试最爱问的几道Java基础问题
本文已经收录自笔者开源的 JavaGuide: https://github.com/Snailclimb ([Java学习+面试指南] 一份涵盖大部分Java程序员所需要掌握的核心知识)如果觉得不错 ...
- BATJ都爱问的多线程面试题
# 一 面试中关于 synchronized 关键字的 5 连击 ### 1.1 说一说自己对于 synchronized 关键字的了解 synchronized关键字解决的是多个线程之间访问资源的同 ...
- 面试 HTTP ,99% 的面试官都爱问这些问题
HTTP 和 HTTPS 的区别 HTTP 是一种 超文本传输协议(Hypertext Transfer Protocol),HTTP 是一个在计算机世界里专门在两点之间传输文字.图片.音频.视频等超 ...
- 这些Servlet知识你一定要知道,金九银十大厂面试官都爱问
前言 Servlet是服务器端的Java应用程序,可以生产动态Web页面.透过JSP执行过程可以知道JSP最终被编译成一个.class文件,查看该文件对应的Java类,发现该Java类继承自org.a ...
- 阿里四面P7稳了,得亏我会这些Spring面试题,果然大厂都爱问它们
前言 先说一下本人情况吧,末流985毕业,毕业之后一直在一家不大不小的公司里安稳上班.上半年因为疫情的原因公司调整了工资,我也是随波逐流跟随大家辞了职.辞职之后向阿里.字节这些都投了简历(但是只收到了 ...
- C#基础原理拾遗——引用类型的值传递和引用传递
以前写博客不深动,只搭个架子,像做笔记,没有自己的思考,也没什么人来看.这个毛病得改,就从这一篇开始- 最近准备面试,深感基础之重要,奈何我不是计算机科班出身,基础方面有些捉襟见肘.短期怎么补?做面实 ...
- 面试都在问的微服务、服务治理、RPC、下一代微服务框架... 一文带你彻底搞懂!
文章每周持续更新,「三连」让更多人看到是对我最大的肯定.可以微信搜索公众号「 后端技术学堂 」第一时间阅读(一般比博客早更新一到两篇) 单体式应用程序 与微服务相对的另一个概念是传统的单体式应用程序( ...
- 面试都在问的「微服务」「RPC」「服务治理」「下一代微服务」一文带你彻底搞懂!
❝ 文章每周持续更新,各位的「三连」是对我最大的肯定.可以微信搜索公众号「 后端技术学堂 」第一时间阅读(一般比博客早更新一到两篇) ❞ 单体式应用程序 与微服务相对的另一个概念是传统的「单体式应用程 ...
随机推荐
- 服务器调用JS
服务器控件调用JS一.两类JS的触发设计1.提交之前的JS -- 加js的事件例:<script language="javascript"> // 构造函数 func ...
- 基于Qt的Tcp协议的流程图
TCP(Transmission Control Protocol传输控制协议)是一种面向连接的.可靠的.基于字节流的传输层通信协议.在qt中,Tcp协议主要是用QTcpServer和QTcpSock ...
- iPhone开发 Swift - NSNotification 通知
Swift创建Notification通知 创建一个SingleView Application 打开AppDelegate.swift,在方法 application(application:UIA ...
- 四.CSS声明
概述 一条CSS规则包括选择符和声明两个部分,声明又包括属性和值,属性指出要影响样式的那个方面,而值用于把属性设定成什么 每个HTML元素有很多属性,可以用CSS来设定,但值却只有少数的几种 CSS属 ...
- 【网络收集】获取JavaScript 的时间使用内置的Date函数完成
var mydate = new Date(); mydate.getYear(); //获取当前年份(2位) mydate.getFullYear(); //获取完整的年份(4位,1970-???? ...
- iframe滚动条的一些方法
用iframe布局,会碰到浏览器右边出现2个滚动条,简单代码解决用iframe布局,会碰到浏览器右边出现2个滚动条, 简单代码解决,用下面的代码可以把浏览器本身的滚动条去掉,只留页面产生的滚动条: b ...
- 【学习笔记】【C语言】sizeof
1.用来计算一个变量或者一个常量.一种数据类型所占的内存字节数. 2.sizeof一共有3种形式 1>sizeof( 变量\常量 ) sizeof(10); char c = 'a'; size ...
- C# 线程--第一单线程基础
概念 什么是进程? 当一个程序被打开运行时,它就是一个进程.在进程中包括线程,进程可以由一个或多个线程组成. 什么是线程? 线程是程序执行流的最小单元.一个标准的线程由线程ID,当前指令指针(PC), ...
- OC4_XML文件解析
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-// ...
- (转)手把手教你如何架设VPN
简介 让远程用户连接Exchange Server的传统解决方案是使用Outlook Web Access.然而,为何不使用虚拟专用网(Virtual Private Network,VPN)让你的远 ...