去年自学C#用的教程是入门级的《学通C#的24堂课》,教材里面也没有提到委托和事件,工作中也没怎么用到。后来一次在网上看了一些大牛的博客,读完之后感觉懵懵懂懂,似懂非懂,过了两三天之后,却又全然忘记了。毕竟学习这事,温故而知新,学了不用,自然忘得也很快。对于如我一样的初学者来说,较好地理解委托和事件并是一件容易的事。其实掌握了的人,会觉得也没什么,而没有掌握的人,每次见到委托和事件就会觉得很畏惧。前段时间看到张旭亮老师的博客中关于.NET 开发系列PPT中提到一个观点,没学会委托就等不会.NET。我深受激励,所以这次下定决心认认真真学习了一下,并将自己的一些理解记录下来。

   委托,字面上的意思就是请别人帮忙做一些事情。读完文章之后你会发现,委托是指向一个方法的指针,通过指定一个委托名称,即可通过委托来调用方法。实际上与字面意思差不多。

  什么情况下适合使用委托

  首先通过一个例子来说明什么情况下使用委托。

  以学生值日为例,值日生要做开门、擦黑板的工作。对应的方法形式如下:

  1. public void OpenDoor(){
  2. //开门具体实现
  3. ...
  4. }
  5.  
  6. public void CleanBlackBoard(){
  7. //擦黑板具体实现
  8. ...
  9. }

  下面再写一个值日的方法,要完成上述这些工作:

  1. public void OnDuty(){
  2. OpenDoor();
  3. CleanBlackBoard();}

  这样,值日生开门、擦黑板的工作就实现了。但是这种方法的扩展性和灵活性不是很好,假设现在值日生工作又增加了,需要关灯,方法如下:

  1. public void TurnOffLight(){
  2. //关灯的具体逻辑
  3. ...
  4. }

  我们就需要修改OnDuty方法:

  1. public void OnDuty(){
  2. OpenDoor();
  3. CleanBlackBoard();
  4. TurnOffLight();
  5. }

  上面的代码没有使用委托,但是还是实现了需要完成的工作。很容易发现,开门、擦黑板、关灯,虽然他们的方法名称不同,但是它们具有相同的“形式”——它们都不获取参数,也都没有返回值(void),这正是委托可以发挥作用的时候。使用于这种形式匹配的一个委托。就可以引用任何工作的方法。我们可以声明如下的一个委托:

  1. public delegate void DoSomeWorkDelegate();

 注意:

  • 声明委托时,要使用 delegate 关键字。
  • 委托定义了它能引用的方法的“形式”。你要指定返回类型(本例是void)、委托名称(DoSomeWorkDelegate)以及任何参数(本例无参数)。

  定义好委托之后,就可以创建它的一个实例,并使用 “+=”操作符,让这个实例引用一个相匹配的方法。代码如下:

  1. public delegate void DoSomeWorkDelegate();
  2. public DoSomeWorkDelegate doSomeWork;
  3.  
  4. doSomeWork+=OpenDoor;

  上例中的方法很简单,既没有返回值,又没有参数。目的只是了解在何种情况下使用委托。下面通过一个带参数的例子来说明继续说明如何使用委托。

  如何使用委托

  此处以听课为例,为了对比,同样先不使用委托,先上代码:

  1. public void AttendClass(string name) {
  2. // 做某些额外的事情,比如初始化之类,此处略
  3. ChineseClass(name);
  4. }
  5. public void ChineseClass(string name) {
  6. //具体听语文课的逻辑
  7. ...  
  8. }

  上面这段代码表示。AttendClass 表示听课,当我们传递代表学生姓名 name 参数,比如说“Jhon”,进去的时候,在这个方法中,将调用ChineseClass 方法,再次传递 Jhon 参数, 表示Jhon 参加了语文课。

  假设现在开设了数学课,我们需要添加新的方法:

  1. public void MathClass(string name){
  2. //具体听数学课的逻辑 ...  
  3. }

  这时候 AttendClass 也要修改,在此之前先定义一个枚举用作判断:

  1. public enum Subject{
  2. Chinese,Math
  3. }
  4.  
  5. public void AttendClass(string name, Subject sub){
  6. //做某些额外的事情,比如初始化之类,此处略
  7. swith(sub){
  8. case Subject.Chinese: ChineseClass(name);
  9. break;
  10. case Subject.Math:
  11. MathClass(name);
  12. break;
  13. }
  14. }

  OK,现在问题解决了,诚如前一个例子所说,假如现在又要增加英语课,体育课,就不得不反复修改枚举和 AttendClass 。可见程序的可拓展性很不好。

如前例,对于上课的方法,不论是什么课,它们都具有相同的“形式”,这样我们即可使用委托。

在考虑如何使用委托之前,我们先看看 AttendClass  的方法签名:

  1. public void AttendClass(string name, Subject sub)

  我们仅看 string name,在这里,string 是参数类型,name 是参数变量,当我们给字符串那么赋值时,我们可以在方法体内对这个name进行其他操作.现在假如现在让 AttendClass() 方法接受一个参数变量,这个变量可以代表另一个方法,当给这个变量赋值为 ChineseClass 时,它代表 ChineseClass() 这个方法,当给它赋值为 MathClass 时,他代表MathClass() 这个方法。就是说让用一个参数来代方法。比如,把这个参数命名为,SubClass ,然后我们在方法体内,就像使用参数一样使用SubClass。由于 SubClass 代表着一个方法,它的使用方式应该和它被赋值的方法(比如 ChineseClass)是一样的,比如

  1. SubClass(string name)

  按照这样的思路这样,AttendClass(),就应该是这个样子的:

  1. public void AttendClass(string name,*** SubClass){
  2. SubClass(name);
  3. }

  注意到 *** ,这个位置通常放置的应该是参数的类型,现在就出现了一个问题:这个代表着方法的SubClass参数应该是什么类型的?

  如果我说答案就是委托。它定义了 SubClass 方法的种类,也就是 SubClass 方法参数的类型。如同 string 决定了AttendClass() 这个方法的第一个参数的类型一样,委托决定 AttendClass 方法中的第二个参数的类型。

  本例中委托的定义如下:

  1. public delegate void ClassDelegate(string name);

  现在再次改动 AttendClass 方法:

  1. public void AttendClass(string name,ClassDelegate SubClass){
  2. SubClass(name);
  3. }

  委托ClassDelegate出现的位置与 string 相同,string是一个类型,那么ClassDelegate 应该也是一个类型,或者叫类(Class)。但是委托的声明方式和类却完全不同。那么本例完整有关上课的类的代码如下:

  1. public delegate void ClassDelegate(string name);
  2. public class OnClass{
  3. //其它代码
  4. ...
  5. public void AttendClass(string name,ClassDelegate SubClass){
  6. SubClass(name);
  7. }
  8.  
  9. public void ChineseClass(string name){
  10. ...
  11. }
  12. public void MathClass(string name){
  13. ...
  14. }
  15. }

  由此可见,委托是一种类型。它定义了方法的类型,实际上就是把方法当做变量。(注意与指针的区别)

  那么我们如何使用它呢,代码如下:

  1. private OnClass objOnClass;
  2. objOnClass=new OnClass();
  3. string name1="xiaoming";
  4. string name2="小张";
  5. objOnClass.AttendClass(name1,ChineseClass);
  6. objOnClass.AttendClass(name2,MathClass);

  上面的代码表示 xiaoming 参加了语文课,小张参加了数学课。既然委托是一种类型。那么我们也可以以下面的方式来运用委托

  1. ClassDelegate delegate1,delegate2;
  2. delegate1=objOnClass.ChineseClass;
  3. delegate2=objOnClass.MathClass;
  4. string name1="xiaoming";
  5. string name2="小张";
  6. objOnClass.AttendClass(name1,delegate1);
  7. objOnClass.AttendClass(name2,delegate2);

  还可以将多个方法付给同一个委托,下面的代码表示小张既参加了语文课,又参加了数学课

  1. ClassDelegate delegate3;
  2. delegate3=objOnClass.ChineseClass;
  3. delegate3+=objOnClass.MathClass;
  4. string name1="小张";
  5. objOnCLass.AttendClass(name1,delegate3);

  可以看到,为委托绑定方法我们使用 重载了的 += 这个符号。如果将上面的代码中第一次绑定方法 delegate3=ChineseClass 改为 delegate3+=ChineseClass,将会出现“使用了未赋值的局部变量”的错误。我们可以像下面这样使用委托,给它初始化赋值

  1. ClassDelegate delegate3=new ClassDelegate(objOnClass.ChineseClass);
  2. string name1="xiaoming";
  3. objOnClass.AttendClass(name1,delegate3);

  我们甚至可以不用AttendClass这个方法,像下面这样来使用委托

  1. ClassDelegate delegate3=new ClassDelegate(objOnClass.ChineseClass);
  2. string name1="xiaoming";
  3. delegate3(name1);

  所以,可以将多个方法绑定到一个委托上面,调用的时候依次执行。

学习和理解C#的委托的更多相关文章

  1. SQL Server 学习博客分享列表(应用式学习 + 深入理解)

    SQL Server 学习博客分享列表(应用式学习 + 深入理解) 转自:https://blog.csdn.net/tianjing0805/article/details/75047574 SQL ...

  2. JDK学习---深入理解java中的HashMap、HashSet底层实现

    本文参考资料: 1.<大话数据结构> 2.http://www.cnblogs.com/dassmeta/p/5338955.html 3.http://www.cnblogs.com/d ...

  3. JDK学习---深入理解java中的LinkedList

    本文参考资料: 1.<大话数据结构> 2.http://blog.csdn.net/jzhf2012/article/details/8540543 3.http://blog.csdn. ...

  4. python基础知识的学习和理解

    参考链接:https://github.com/yanhualei/about_python/tree/master/python_learning/python_base   python基础知识笔 ...

  5. 【log4j】的学习和理解 + 打印所有 SQL

    log4j 1.2 学习和理解 + 打印所有 SQL 一.基本资料 官方文档:http://logging.apache.org/log4j/1.2/manual.html(理解基本概念和其他) lo ...

  6. 学习和理解C#中的事件

    注:本文系学习笔记. 上一篇文章记录了我对C#中委托的理解.委托实际上是一种类型.可以将一个或多个方法绑定到委托上面,调用委托时,一次执行委托上面绑定的方法.本文要讲述的事件实际上和委托有很深的“感情 ...

  7. STM32学习笔记(四) RCC外设的学习和理解

    RCC时钟模块并不好理解,初次接触我也是一头雾水,而且我真正掌握它的时候也比较晚,是我在学习uC/os-II,需要分析时钟时才有了深刻认识.但在学习中我却一定要把放在了前列,因为这是整个嵌入式最重要的 ...

  8. PHP面向对象三大特点学习(充分理解抽象、封装、继承、多态)

    PHP面向对象三大特点学习 学习目标:充分理解抽象.封装.继承.多态   面象对向的三大特点:封装性.继承性.多态性 首先简单理解一下抽象:我们在前面定义一个类的时候,实际上就是把一类事物共有的属性和 ...

  9. 《C#高级编程》学习笔记------C#中的委托和事件(续)

    本文转载自张子阳 目录 为什么要使用事件而不是委托变量? 为什么委托定义的返回值通常都为void? 如何让事件只允许一个客户订阅?(事件访问器) 获得多个返回值与异常处理 委托中订阅者方法超时的处理 ...

随机推荐

  1. Protected Functions 是理解OO的难点和关键

    Protected Functions 是理解OO的难点和关键 private和public函数都好理解,这里就不多说了,夹在中间的prortected却有许多精妙之处,说说我的几个疑问和看法:1. ...

  2. UVA548——Tree(中后序建树+DFS)

    Tree You are to determine the value of the leaf node in a given binary tree that is the terminal nod ...

  3. RPi 2B apache2 mysql php5 and vsftp

    /************************************************************************* * RPi 2B apache2 mysql ph ...

  4. 【原】android通过adb wireless的使用

    开发android程序,总是需要插拔插拔的,usb口都给弄坏掉了,现在adb可以通过无线网,链接处在同一个局域网下面的android设备 1.将电脑和移动设备链接到同一个无线网下 2.在android ...

  5. Python中,如何初始化不同的变量类型为空值

    参考文章  Python中,如何初始化不同的变量类型为空值 常见的数字,字符,很简单,不多解释. 列表List的其值是[x,y,z]的形式 字典Dictionary的值是{x:a, y:b, z:c} ...

  6. 任务栏窗口和状态图标的闪动 z

    Demo程序: 实现任务栏窗体和图标的闪动: 整个程序是基于Windows Forms的,对于任务栏右下角状态图标的闪动,创建了一个类型:NotifyIconAnimator,基本上是包装了Windo ...

  7. Uva 10480 Sabotage 最大流

    表示自从学了网络流,就基本上是一直用dinic 这个题一看就是用最大流,作为常识,两个点之间的最大流等于最小割 但是这个题需要输出割边,然后我就不会了,dinic判流量我觉得也可做,但是一直wa 然后 ...

  8. REST API TESTING

    在敏捷开发过程中 每隔两周就是一个sprint,,, 在上个sprint中,任务就是REST API TESTING 因为以前没做过API 测试,不懂,然后经过询问查找 终于知道,需要发送请求,然后获 ...

  9. opencv开发的程序分发给客户时所需要的dll文件

    这里主要讲在其他裸机,没有搭建开发环境机器上运行自己开发的程序. 为了测试,我专门用visualbox搭建了一个虚拟机(主机和虚拟机都是win7系统) 在发给别人程序运行出现错误:msvcp100d. ...

  10. Stm32外围模块编程初始化步骤

    Stm32外围模块编程初始化步骤: 一.外部中断 1)初始化 IO 口为输入. 这一步设置你要作为外部中断输入的 IO 口的状态,可以设置为上拉/下拉输入,也可以设置为浮空输入,但浮空的时候外部一定要 ...