C#中委托和事件是很重要的组成部分,而掌握委托和事件的本质将必不可少。为了能探秘本质,写了如下代码

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks; namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
Water water = new Water(); water.WaterBoils += water_WaterBoils;
water.WaterBoils += water_WaterBoils2; water.DegreeRise();
Console.ReadLine();
} static void water_WaterBoils(object sender, WaterBoilsEventArgs args)
{
Console.WriteLine(string.Format("sender:{0}", sender.ToString()));
Console.WriteLine(string.Format("args:{0}", args.CurentDegree.ToString()));
}
public static void water_WaterBoils2(object sender, WaterBoilsEventArgs args)
{
Console.WriteLine(string.Format("sender_2:{0}", sender.ToString()));
Console.WriteLine(string.Format("args_2:{0}", args.CurentDegree.ToString()));
}
} public delegate void WaterBoilsEventHandler(object sender,WaterBoilsEventArgs args); public class WaterBoilsEventArgs : EventArgs
{
public int CurentDegree { get; set; }
} public class Water
{
public event WaterBoilsEventHandler WaterBoils;
public int curentDegree = ; public void DegreeRise()
{
for(var n = ;n<;n++)
{
Thread.Sleep(); if(n>=)
{
if(WaterBoils!=null)
{
WaterBoils(this,new WaterBoilsEventArgs(){CurentDegree = n}); //WaterBoils.Invoke(this, new WaterBoilsEventArgs() { CurentDegree = n });
}
}
}
}
}
}

介绍一下这段代码:定义了一个委托WaterBoilsEventHandler,一个类Water,Water的DegreeRise方法触发WaterBoils事件,一个事件参数WaterBoilsEventArgs ,一个Main方法.

既然是要看委托了事件的本质,所以借助反编译工具(Reflector)查看编译后的代码片段:

首先来看一下委托编译以后的代码:

.class public auto ansi sealed WaterBoilsEventHandler
extends [mscorlib]System.MulticastDelegate
{
.method public hidebysig specialname rtspecialname instance void .ctor(object 'object', native int 'method') runtime managed
{
} .method public hidebysig newslot virtual instance class [mscorlib]System.IAsyncResult BeginInvoke(object sender,
class ConsoleApplication1.WaterBoilsEventArgs args, class [mscorlib]System.AsyncCallback callback, object 'object') runtime managed
{
} .method public hidebysig newslot virtual instance void EndInvoke(class [mscorlib]System.IAsyncResult result) runtime managed
{
} .method public hidebysig newslot virtual instance void Invoke(object sender, class ConsoleApplication1.WaterBoilsEventArgs args)
 runtime managed
{
} }

从反编译IL代码中可以看出:

1.自定义委托的本质是一个类,具有Invoke,BeginInvoke,Endinvoke方法分别来支持同步和异步的执行,Invoke无返回值;BeginInvoke返回IAsyncResult,IAsyncResult可以作为EndInvoke的参数来结束执行。

2.自定义委托继承自MulticastDelegate,使自定义委托具有“组播”的能力,也就是可以绑定多个委托;(MulticastDelegate类又继承自Delegate

下面来看看“组播”是怎么实现的:MulticastDelegate内有两个方法CombineImpl和RemoveImpl分别完成委托的绑定和解绑定;内部有一个对象(使用中会转换成object[])_invocationList存储MulticastDelegate对象;还有其他的一些方法共同完成。

从上边可以看出,委托是可以绑定执行多个方法的,那为什么还要事件呢,我觉得这个问题可能从语言设计角度上讲,委托的设计出发点是规避犹如在C、C++等中的指针变量,委托具把这个地址进行了包装,反编译Delegate类,发现有如下成员

   _methodPtr:方法的指针,一个Delegate对象维护了一个方法的引用地址

在使用委托的是否发现有有悖的地方:《 C# 与 .Net 3.5 高级程序设计第四版》有如下解释:

“如果我们没有把委托成员变量定义为私有的,调用者就可以直接访问委托对象,这样调用者就可以把变量赋值为新的委托对象(实际上也就是删除了当前要调用的方法列表),更糟糕的是,调用者可以直接调用委托的调用列表。”

这里就有一个比较严重的问题:1.比如两个对象去注册,A已经注册了一个方法,B去注册的时候把A注册的方法给删除了,并且B还可以操作到A注册的方法。这种不友好是不能忍受的。

C#提供了event关键字来为我们解决问题,使用过event的童鞋们都可能有印象,在声明事件以外的其他类中,如果调用事件,只能干两件事情:

  1.绑定方法引用,

  2.解绑方法引用。

这是对事件的限制。

顺着这条思路,看看C#中事件是怎么完成的。

首先看一看,Water类的反编译结果

.class public auto ansi beforefieldinit Water
extends [mscorlib]System.Object
{
.event ConsoleApplication1.WaterBoilsEventHandler WaterBoils
{
.addon instance void ConsoleApplication1.Water::add_WaterBoils(class ConsoleApplication1.WaterBoilsEventHandler)
.removeon instance void ConsoleApplication1.Water::remove_WaterBoils(class ConsoleApplication1.WaterBoilsEventHandler)
} .method public hidebysig specialname rtspecialname instance void .ctor() cil managed
{
} .method public hidebysig instance void DegreeRise() cil managed
{
} .field public int32 curentDegree .field private class ConsoleApplication1.WaterBoilsEventHandler WaterBoils }

add_WaterBoils方法的方法定义如下:

public void add_WaterBoils(WaterBoilsEventHandler value)
{
WaterBoilsEventHandler handler2;
WaterBoilsEventHandler waterBoils = this.WaterBoils;
do
{
handler2 = waterBoils;
WaterBoilsEventHandler handler3 = (WaterBoilsEventHandler) Delegate.Combine(handler2, value);
waterBoils = Interlocked.CompareExchange<WaterBoilsEventHandler>(ref this.WaterBoils, handler3, handler2);
}
while (waterBoils != handler2);
}

remove_WaterBoils方法的方法定义如下

public void remove_WaterBoils(WaterBoilsEventHandler value)
{
WaterBoilsEventHandler handler2;
WaterBoilsEventHandler waterBoils = this.WaterBoils;
do
{
handler2 = waterBoils;
WaterBoilsEventHandler handler3 = (WaterBoilsEventHandler) Delegate.Remove(handler2, value);
waterBoils = Interlocked.CompareExchange<WaterBoilsEventHandler>(ref this.WaterBoils, handler3, handler2);
}
while (waterBoils != handler2);
}

可以看出:

1.增加了两个方法:add_WaterBoils 和 remove_WaterBoils;

2.还增加了一个WaterBoilsEventHandler 类型的 私有变量 WaterBoils且与声明的事件对象名相同

这里容易产生一点迷惑:.event ConsoleApplication1.WaterBoilsEventHandler WaterBoils 有点像一个内部类的结构,且这个“类”具有两个方法。暂时把这个迷惑放下。

3.add_WaterBoils和remove_WaterBoils方法都是对私有字段WaterBoils的维护。

知道这些后,再开看看注册的时候是怎么调用的:

private static void Main(string[] args)
{
Water water = new Water();
water.WaterBoils += new WaterBoilsEventHandler(Program.water_WaterBoils);
water.WaterBoils += new WaterBoilsEventHandler(Program.water_WaterBoils2);
water.DegreeRise();
Console.ReadLine();
}

这是使用的C#代码方式,看不出来什么结果,查看IL代码如下:

.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
.maxstack
.locals init (
[] class ConsoleApplication1.Water water)
L_0000: nop
L_0001: newobj instance void ConsoleApplication1.Water::.ctor()
L_0006: stloc.0
L_0007: ldloc.0
L_0008: ldnull
L_0009: ldftn void ConsoleApplication1.Program::water_WaterBoils(object, class ConsoleApplication1.WaterBoilsEventArgs)
L_000f: newobj instance void ConsoleApplication1.WaterBoilsEventHandler::.ctor(object, native int)
L_0014: callvirt instance void ConsoleApplication1.Water::add_WaterBoils(class ConsoleApplication1.WaterBoilsEventHandler)
L_0019: nop
L_001a: ldloc.0
L_001b: ldnull
L_001c: ldftn void ConsoleApplication1.Program::water_WaterBoils2(object, class ConsoleApplication1.WaterBoilsEventArgs)
L_0022: newobj instance void ConsoleApplication1.WaterBoilsEventHandler::.ctor(object, native int)
L_0027: callvirt instance void ConsoleApplication1.Water::add_WaterBoils(class ConsoleApplication1.WaterBoilsEventHandler)
L_002c: nop
L_002d: ldloc.0
L_002e: callvirt instance void ConsoleApplication1.Water::DegreeRise()
L_0033: nop
L_0034: call string [mscorlib]System.Console::ReadLine()
L_0039: pop
L_003a: ret
}

重点分析一下 L_0009 到 L_0014 的这三行代码:

L_0009 :把water_WaterBoils方法的指针推送到计算堆栈上

L_000f  :创建一个WaterBoilsEventHandler委托对象

L_0014 :调用add_WaterBoils,并把结果推送到计算堆栈上

同时请注意:“ConsoleApplication1.Water::add_WaterBoils”这句代码,add_WaterBoils是ConsoleApplication1.Water类对象的方法,上边的迷惑,看着像一个“内部类”,实则没那关系。

这段代码让我们明白:事件的注册是调用编译生成的方法 add_WaterBoils 把 委托中带有的方法引用地址 维护到编译生成的私有属性 WaterBoils 中去了。

所以event就是语法糖,节省了编写代码的时间,并且事件的触发使用专门的事件来处理,显出语言本身的完整,真正的实现是编译器生成委托对象和其他处理代码实现的

最近面试,遇到一个问题:“事件是不是一种委托?”

来看一下事件对象是什么

在快速监视器中查看:

可以看出,申明的事件对象( water.WaterBoils) 是委托(WaterBoilsEventHandler)实例。

“ .event ConsoleApplication1.WaterBoilsEventHandler WaterBoils”中的 “.event”  则表示 “WaterBoils”具有特殊性,是事件。

我的理解:

  C#中的事件就是一种语法糖,不是一些人认为的是对委托的封装,它是事件处理的一种指导性应用方式,event使其具有一定的约束性,带有“最小化影响”的设计原则(不允许影响他人的注册),它是依靠委托来实现的,但它不是一种委托,它是比委托更抽象的一种应用方式。

  而事件对象本身就是委托实例(CLR可能在运行的时候会把事件对象与相同名称的私有field进行关联,比如调试的时候可以看出,事件对象的值就是该相同名称的私有field的值)。

欢迎大家谈谈自己的看法

希望与大家多多交流  QQ:373934650;群Q:227231436

如果有用请大伙儿帮忙推荐一下,谢谢!

C#委托和事件本质的更多相关文章

  1. 《C#从现象到本质》读书笔记(六)第8章委托和事件

    <C#从现象到本质>读书笔记(六)第二部分 C#特性 第8章委托和事件 从这一部分开始,知识点就相对少了,重要的是代码练习.奈何太高深的代码平常不怎么用,这些特性也不是经常写代码的. 委托 ...

  2. 【详细】【转】C#中理解委托和事件 事件的本质其实就是委托 RabbitMQ英汉互翼(一),RabbitMQ, RabbitMQ教程, RabbitMQ入门

    [详细][转]C#中理解委托和事件   文章是很基础,但很实用,看了这篇文章,让我一下回到了2016年刚刚学委托的时候,故转之! 1.委托 委托类似于C++中的函数指针(一个指向内存位置的指针).委托 ...

  3. C# 委托/事件本质详解

    委托 一.什么是委托 IL层面1>委托的本质就是一个类2>继承自System.MulticastDelegate3>委托里面内置了3个方法:Invoke(),BeginInvoke( ...

  4. C#基础篇 - 理解委托和事件

    1.委托 委托类似于C++中的函数指针(一个指向内存位置的指针).委托是C#中类型安全的,可以订阅一个或多个具有相同签名方法的函数指针.简单理解,委托是一种可以把函数当做参数传递的类型.很多情况下,某 ...

  5. .NET面试题系列[7] - 委托与事件

    委托和事件 委托在C#中具有无比重要的地位. C#中的委托可以说俯拾即是,从LINQ中的lambda表达式到(包括但不限于)winform,wpf中的各种事件都有着委托的身影.C#中如果没有了事件,那 ...

  6. .NET基础拾遗(4)委托、事件、反射与特性

    Index : (1)类型语法.内存管理和垃圾回收基础 (2)面向对象的实现和异常的处理基础 (3)字符串.集合与流 (4)委托.事件.反射与特性 (5)多线程开发基础 (6)ADO.NET与数据库开 ...

  7. .NET委托和事件

    .net学习之委托和事件   1.什么是委托 通俗的说:委托就是一个能够存储符合某种格式(方法签名)的方法的指针的容器 上传图片: 2.委托语法 准备一个方法:string Hello(string ...

  8. C#编程之委托与事件四(二)【转】

    C#编程之委托与事件(二)       我在上一篇文章(C#编程之委托与事件(一) )中通过示例结合的方法介绍了委托,在本文中,我同样以代码示例的方式来介绍C#里的事件机制. 二.事件   1.了解概 ...

  9. C#编程之委托与事件四(一)【转】

    C#编程之委托与事件(一)     本文试图在.net Framework环境下,使用C#语言来描述委托.事件的概貌.希望本文能有助于大家理解委托.事件的概念,理解委托.事件的用途,理解它的C#实现方 ...

随机推荐

  1. jquery如何将获取的颜色值转换为十六进制形式

    jquery如何将获取的颜色值转换为十六进制形式:大家或许已经注意到了,在谷歌.火狐和IE8以上浏览器中,获取的颜色值是RGB形式,例如rgb(255,255,0),感觉非常不适应,或者在实际编码中不 ...

  2. CRM窗体中只读的控件不会引发Update事件

    在CRM的窗体设计时,如果把某一个控件设为只读了,仅管你在后台用代码修改了值,这个值也不会起任何作用,更不会提交到后台,触发Update事件!

  3. 织梦DedeCMS子目录移动到根目录的方法

    有时候我们在子目录中安装了dedecms,但有时候需要将其换到根目录中,下面就讲一下织梦DedeCMS子目录移动到根目录的方法: 下面是具体的操作步骤,强烈建议先备份数据库. 1.进入dedecms后 ...

  4. UVA - 1614 Hell on the Market(贪心)

    Time Limit: 3000MS   Memory Limit: Unknown   64bit IO Format: %lld & %llu Submit Status Descript ...

  5. Memcached源码分析——hash

    以下为memcached中关于使用的hash算法的一点记录 memcached中默认使用的是Bob Jenkins的jenkins_hash算法 以下4段代码均在memcached-1.4.22/ha ...

  6. 分布式配置管理平台 - Disconf介绍

    原博客地址:http://blog.csdn.net/zhu_tianwei/article/details/47984545 Disconf专注于各种分布式系统配置管理的通用组件/通用平台,提供统一 ...

  7. Solr4.8.0源码分析(17)之SolrCloud索引深入(4)

    Solr4.8.0源码分析(17)之SolrCloud索引深入(4) 前面几节以add为例已经介绍了solrcloud索引链建索引的三步过程,delete以及deletebyquery跟add过程大同 ...

  8. UOJ 52 元旦激光炮

    http://uoj.ac/problem/52 题意:每次可以得到3个序列中 思路:每次分别取出三个序列的K/3长度的位置,取最小的那个,然后每次减掉它,总复杂度是Nlog3N #include & ...

  9. COJ 1011 WZJ的数据结构(十一)树上k大

    题解:主席树&DFS序. PS:为什么我一开始Wa了N发 是因为有一个左区间我写成[L,M+1]了.......................... #include<iostream ...

  10. 【线段树】HDU 5443 The Water Problem

    题目链接: http://acm.hdu.edu.cn/showproblem.php?pid=5443 题目大意: T组数据.n个值,m个询问,求区间l到r里的最大值.(n,m<=1000) ...