C#委托与事件学习笔记

      今天跟随视频学习了一下C#中最重要的一些概念之委托与事件。老杨的视频讲的还是挺深入浅出,不过刚接触C#.NET的人还是朦朦胧胧,就像张子阳先生说的“每次见到委托和事件就觉得心里别(biè)得慌,混身不自在”。跨过这道坎的人就有种一览众山小的感觉了。我又浏览了皱华栋老师JamesZou的博文《深入理解C#委托及原理》(地址:http://www.cnblogs.com/jameszou/archive/2011/07/21/2112497.html),以及张子阳Jimmy Zhang的博文《C# 中的委托和事件》(地址:http://www.cnblogs.com/jimmyzhang/archive/2007/09/23/903360.html)总算对委托有了一点理性的感觉了,在此谢谢ITCAST,JamesZou以及Jimmmy Zhang的博文,谢谢。

1.委托是神马?

  用最通俗易懂的话来讲,你就可以把委托看成是用来执行方法(函数)的一个“指针”。用邹老师的一个举例:“设想,如果我们写了一个厨师做菜方法用来做菜,里面有拿菜、切菜、配菜、炒菜 四个环节,但编写此方法代码的人想让配菜这个环节让调用方法的人实现,换句话说,就是想在方法被调用时接收代码 作为参数,在方法中执行这端传进来的代码。但,怎么为一个方法传 代码 进来呢?当然大家想到了传递接口方式来实现,咱先不讨论接口,因为微软为我们提供了一个叫做委托的类型。”

  现在来看看怎样使用委托,根据itcast的ppt内容:

  声明委托的方式:delegate 返回值类型 委托类型名(参数) 比如delegate void StringProcess(string s); 注意这里的除了前面的delegate,剩下部分和声明一个函数一样,但是StringProcess不是函数名,而是委托类型名   声明的委托是一种类型,就像int、Person一样,如果要用的话还要声明委托类型的变量,声明委托类型变量的方式:StringProcess f1;   将委托类型变量指向函数 StringProcess sp = new StringProcess(SayHello),这样就可以像调用普通函数一样把sp当成函数用了。委托可以看做是函数的指针。整数可以用整数变量指向它,对象可以用对象变量指向它,函数也可以用委托变量指向它。和直接调用函数的区别:用委托就可以指向任意的函数,哪怕是之前没定义的都可以,而不使用受限于那几种。   将委托类型变量指向函数还可以简化成StringProcess sp = SayHello,编译器帮我们进行了new。但是不能sp=PrintIt(),因为这样就成了“执行PrintIt函数,并且将sp指向PrintIt的返回值”。

  这里看一个数据过滤的例子,输出int数组中的正整数:

  1.声明一个委托:delegate bool FilterDelegate(int i);

  2.封装一个过滤的静态方法,参数中包含一个过滤器的方法委托,返回泛型List<int>列表:

  static List<int> Filter(List<int> list,FilterDelegate fd)         {             List<int> listTest = new List<int>();             foreach(int i in list)             {                 if(fd(i))                 {                     listTest.Add(i);                 }             }             return listTest;         }

  3.写一个判断是否为正整数的方法,返回值为bool类型:

     static bool isZhengshu(int i)         {             return i > 0;         }

  4.在main函数中声明一个List列表,然后添加部分测试数据,将委托指向判断正整数的方法,最后遍历输出过滤后的数组数据;

  List<int> listOne = new List<int>();       listOne.Add(1);       listOne.Add(-4);       listOne.Add(8);       listOne.Add(-6);       listOne.Add(13);

      FilterDelegate fd = isZhengshu;       List<int> listResult = Filter(listOne, isZhengshu);       foreach (int i in listResult)       {            Console.WriteLine(i);       } 

  运行后,显示:1 8 13 

  通过一个小例子,可以得出一个小结论:C# 中的委托类似于 C 或 C++ 中的函数指针。使用委托使程序员可以将方法引用封装在委托对象内。然后调用该委托对象就可以执行委托对象内方法引用指向的方法,而不必在编译时知道将调用哪个方法(如参数为委托类型的方法,也就是提供了为程序回调指定方法的机制)。”摘录自MSDN;

  邹老师的通俗说法是:“就是一个能存放很多方法的指针的调用清单(但方法签名必须和委托类型签名一样),你一调用这个清单,那么清单里的所有的指针所对应的方法就会依次被执行。”

  而委托的原理是神马?这里就需要跟随邹老师的博文走走了,通过VS中自带的MSIL反编译程序,将生成后的.exe拖到工具中查看委托类型声明的代码,发现其编译前就生成了一个类;它继承了System.MulticastDelegate,包含了构造方法、BeginInvoke、EndInvoke、Invoke方法。另外MulticastDelegate则继承自Delegate类。通过Reflector反编译工具,可以看出:继承关系:编译前生成的类 –> MulticastDelegate–> Delegate,而MulticastDelegate类中有3个重要的成员,其中两个继承自 Delegate:  

  这三者的作用分别是:

  _methodPtr 里保存的就是 方法指针。

  _target 里用来保存方法所在的对象。

  _invocationList 其实使用时是个object数组,在注册多个方法时,其他方法就保存在此成员中,而它也就是 委托链 的关键容器。--摘自邹老师的博文;

2.事件闪亮出场 

  下面来看一个通过委托实现打招呼Greeting的例子(感谢张子阳先生的博文,此例选自其博文)

  1.两种不同的Greeting方式:

   static void ChineseGreeting(string name)         {             Console.WriteLine("早上好,"+name);         }

        static void EnglishGreeting(string name)         {             Console.WriteLine("Morning,"+name);         }

  2.声明一个委托:

  public delegate void GreetingDelegate(string name);

  3.封装一个类,其中包含一个事件:

  public class GreetingManager     {         public GreetingDelegate onGreeting;

        public void ProcessGreeting(string name)         {             if (onGreeting != null)             {                 onGreeting(name);             }         }     }

  4.客户端调用:

  GreetingManager gm = new GreetingManager();       gm.onGreeting += EnglishGreeting;       gm.onGreeting += ChineseGreeting;       gm.ProcessGreeting("Edison Chou");

  5.运行显示结果:

  Hello,Edison Chou

  你好,Edison Chou

  这里有一个问题,如果将委托声明为private权限,那么:“这简直就是在搞笑。因为声明委托的目的就是为了把它暴露在类的客户端进行方法的注册,你把它声明为private了,客户端对它根本就不可见,那它还有什么用?”-(摘自张子阳的原话)

那么如果就将委托设置为public,则客户端可以清空监听(即设置为null,因为它是引用类型),也可以伪造监听(即直接调用委托),破坏了其封装性。最后,第一个方法注册用“=”,是赋值语法,因为要进行实例化,第二个方法注册则用的是“+=”。但是,不管是赋值还是注册,都是将方法绑定到委托上,除了调用时先后顺序不同,再没有任何的分别,这样不是让人觉得很别扭么?

  该怎么解决呢?于是Event事件闪亮登场了!!!它封装了委托类型的变量,使得:在类的内部,不管你声明它是public还是protected,它总是private的。在类的外部,注册“+=”和注销“-=”的访问限定符与你在声明事件时使用的访问符相同。

于是,我改写GreetingManager类:

  public class GreetingManager     {         public event GreetingDelegate onGreeting;

        public void ProcessGreeting(string name)         {             if (onGreeting != null)             {                 onGreeting(name);             }         }     }

 这里仅仅是加了一个event标志,没有神马大的改变。但通过Reflector反编译,可以看出事件其实就是一个封装了的私有的委托而已,还包含两个方法:add和remove;这两个方法分别用于注册委托类型的方法和取消注册。实际上也就是: “+= ”对应 add_ProcessGreeting,“-=”对应remove_ProcessGreeting。而这两个方法的访问限制取决于声明事件时的访问限制符。所以,这下客户端只能注册、注销事件,无法进行伪造和清空事件,保证了封装性。

3.委托和事件的区别

  委托和事件没有可比性,因为委托是类型,事件是对象。而委托的对象(用委托方式实现的事件)与标准event方式实现的事件的区别是:事件的内部是用委托实现的。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏技术博客

C#简单的面试题目(三)

31.C#提供一个默认的无参构造函数,当我实现了另外一个有一个参数的构造函数时,还想保留这个无参数的构 造函数。这样我应该写几个构造函数?     两个,一...

31910
来自专栏小樱的经验随笔

记一次拿webshell踩过的坑(如何用PHP编写一个不包含数字和字母的后门)

这一串代码描述是这样子,我们要绕过A-Za-z0-9这些常规数字、字母字符串的传参,将非字母、数字的字符经过各种变换,最后能构造出 a-z 中任意一个字符,并且...

19820
来自专栏大内老A

深入理解string和如何高效地使用string

无论你所使用的是哪种编程语言,我们都不得不承认这样一个共识:string是我们使用最为频繁的一种对象。但是string的常用性并不意味着它的简单性,而且我认为,...

245100
来自专栏技术博客

C# try catch finally

 catch 和 finally 一起使用的常见方式是:在 try 块中获取并使用资源,在 catch 块中处理异常情况,并在 finally 块中释放资源。

43620
来自专栏C/C++基础

C++IO流简介

输入输出(IO)是指计算机同任何外部设备之间的数据传递。常见的输入输出设备有文件、键盘、打印机、屏幕等。数据可以按记录(或称数据块)的方式传递,也可以 流的方式...

15230
来自专栏技术记录

ReflectASM-invoke,高效率java反射机制原理

前言:前段时间在设计公司基于netty的易用框架时,很多地方都用到了反射机制。反射的性能一直是大家有目共睹的诟病,相比于直接调用速度上差了很多。但是在很多地方,...

618150
来自专栏Java编程

《Effective Java》——读后总结

这本书在Java开发的行业里,颇有名气。今天总算是粗略的看完了...后面线程部分和序列化部分由于心浮气躁看的不仔细。这个月还剩下一周,慢慢总结消化。

46610
来自专栏yang0range

Java的面试基础题(二)

1)特点:存储对象;长度可变;存储对象的类型可不同 2)Collection (1)List:有序的;元素可重复,有索引 (add(index, elem...

19020
来自专栏C/C++基础

C++中mutable关键字的用法

mutalbe的中文意思是“可变的,易变的”,是constant(即C++中的const)的反义词。在C++中,mutable也是为了突破const的限制而设置...

6010
来自专栏xx_Cc的学习总结专栏

iOS底层原理总结 - 探寻block的本质(一)

28740

扫码关注云+社区

领取腾讯云代金券