定义:委托是一种引用类型,表示对具有特定参数列表和返回类型的方法的引用。 在实例化委托时,你可以将其实例与任何具有兼容签名和返回类型的方法相关联

目的:方法声明和方法实现的分离,使得程序更容易扩展

一、对委托的理解

1. 为什么将方法作为另一个方法的参数

先不解释定义,看一段代码

  1. public void Method1(object obj)
  2. {
  3. //内部可以访问obj的成员
  4. }

这是随便写的一个方法,没有实际意义,但是,根据我们已掌握的关于类型的基础知识,应该明白,这里的obj(引用类型)作为形参,存放的是对象的引用,既然获取到了对象的引用,那么我们可以在run方法内部对obj的成员进行访问(一段废话)。好了,现在我要问一个问题:为什么要将obj作为参数?

问题先慢慢想着,我们再次看一下委托的定义,"是引用类型,对方法的引用",看下面的代码

  1. public void Method2(delegate del)
  2. {
  3. //内部可以访问del的什么?
  4. //只能执行方法
  5. del();
  6. }

delegate 作为一种引用类型,引用的是个方法,我们能对方法做什么,只能执行方法。

下面我们回答刚才的问题,obj作为参数(类型声明),将类型的声明和类型的实例分离。当然这样做的目的是为了封装变化,所有类型都可以作为实参来使用Method1,因为Object是基类,当然也可以将Object换成其他接口类型,只要实现了该接口的类型都可以作为Method1的实参。

虽然Method1和Method2参数类型不一样,但是目的是一致的,委托是将方法的声明和实现分离。

下面看一个网上使用广泛的例子

  1. 示例1
  2. //定义委托,与任何具有兼容签名和返回类型的方法相关联
  3. public delegate void GreetingDelegate(string name);
  4. class Program
  5. {
  6. private static void EnglishGreeting(string name)
  7. {
  8. Console.WriteLine("Morning, " + name);
  9. }
  10. private static void ChineseGreeting(string name)
  11. {
  12. Console.WriteLine("早上好, " + name);
  13. }
  14. //将委托类型GreetingDelegate作为形参声明
  15. private static void GreetPeople(string name, GreetingDelegate MakeGreeting)
  16. {
  17. //这里可以完成其他的业务逻辑
  18. //然后调用委托
  19. MakeGreeting(name);
  20. }
  21. static void Main(string[] args)
  22. {
  23. GreetPeople("hanmeimei", EnglishGreeting);//使用静态方法初始化委托
  24. GreetPeople("韩梅梅", new Program().ChineseGreeting);//使用实例方法初始化委托
  25. Console.ReadKey();
  26. }
  27. }

委托类型GreetingDelegate作为形参声明,只要是具有兼容签名和返回类型的方法都可以作为GreetPeople方法的实参。这样一来,GreetPeople方法不仅可以通过中文和英文问好了,所有与委托类型GreetingDelegate相关联的语言(方法)都可以问好了,程序更容易扩展了。

2. 委托是一种引用类型

既然委托是一种类型,应该包含类型的成员,我们将代码编译完成后,借助反编译工具看下,编译后的样子:



下面这两种方式是等同的:

  1. MakeGreeting(name);
  2. MakeGreeting.Invoke(name);

而BeginInvoke和EndInvoke是属于异步调用的范畴,我们稍后再说。

3.Lambda表达式比匿名方法简约

从示例1中可以看到分别显示调用了静态方法和实例方法初始化了委托,但是对于那些只使用一次的方法,就没有必要创建具名方法了。C#2.0提出了使用匿名方法代替具名方法的解决方案

  1. GreetingDelegate MakeGreeting = delegate (string name)
  2. {
  3. Console.WriteLine("早上好, " + name);
  4. };
  5. GreetPeople("韩梅梅", MakeGreeting);

匿名方法仍然比较繁琐,C#3.0引入了Lambda表达式

  1. //去掉delegate关键字并添加 =>运算符
  2. GreetingDelegate MakeGreeting = (string name) =>
  3. {
  4. Console.WriteLine("早上好, " + name);
  5. };
  6. GreetPeople("韩梅梅", MakeGreeting);
  7. //根据委托的参数类型推断,string类型也去掉了
  8. GreetingDelegate MakeGreeting = name =>
  9. {
  10. Console.WriteLine("早上好, " + name);
  11. };
  12. GreetPeople("韩梅梅", MakeGreeting);
4. 委托也是类型安全的
  1. GreetingDelegate MakeGreeting1 = (int name) =>
  2. {
  3. Console.WriteLine("早上好, " + name);
  4. };

5. 泛型委托的协变和逆变

常用泛型委托

  • Action
  • Func

关于泛型委托的协变和逆变可以阅读这篇文章《C#基本功之泛型》

有了常用泛型委托,我们就不需要自己定义GreetingDelegate委托类型了,对委托的使用进一步的简化了。

  1. //用泛型委托声明形参,Func于此类似,只不过有返回值而已
  2. private static void GreetPeople(string name,Action<string> action)
  3. {
  4. //这里可以完成其他的业务逻辑
  5. //然后调用委托
  6. action.Invoke(name);
  7. }
  8. //调用
  9. GreetPeople("韩梅梅", name => Console.WriteLine("早上好, " + name))

到目前为止,我们将方法的变化抽象,并用委托封装,实现了委托的简单使用。但委托还有很大的用处。

委托是类的成员,我们看一个作为类的成员使用的例子:现在生活中智能设备越来越普及,以前需要自己动手拉开窗帘、打开热水器等等,现在只需要设定场景,利用智能设备就可以完成这些操作。

当时间定格为早上7点的时候,闹钟想起,窗帘自动打开,热水器开始烧水,加湿器关闭.....

  1. //先不用管EventArgs参数,object 类型的sender,可以理解为任何类型都可以传递
  2. public delegate void EventHandler(object sender, EventArgs e);
  3. /// <summary>
  4. /// 控制中心
  5. /// </summary>
  6. public class ControlCore
  7. {
  8. public DateTime Time { get; set; } = DateTime.Now;
  9. /// <summary>
  10. /// 执行任务
  11. /// </summary>
  12. public event EventHandler Task;
  13. }
  14. static void Main(string[] args)
  15. {
  16. var controlCore= new ControlCore();
  17. controlCore.Task= new EventHandler(AlarmClock);
  18. //怎么操作委托?
  19. //添加、移除
  20. controlCore.Task+=....;
  21. controlCore.Task-=....;
  22. //或者直接覆盖掉
  23. controlCore.Task=....;
  24. controlCore.Task(null, null);
  25. }
  26. /// <summary>
  27. /// 闹钟
  28. /// </summary>
  29. public static void AlarmClock(object sender, EventArgs e)
  30. {
  31. Console.WriteLine("起床了,亲");
  32. }

作为类的成员时,要怎么操作委托?我们都知道委托还可以添加或移除方法,所以我们不仅可以直接调用委托,还可以添加、移除或者直接覆盖委托,对委托的操作没有任何限制。如此一来,破坏了类的封装性。我们希望对委托有一些限制,就像用属性去限制字段的输入输出一样;

  1. //将委托的访问级别改为private
  2. private EventHandler Task;
  3. //为委托添加方法
  4. public void AddTask(EventHandler handler)
  5. {
  6. if (this.Task== null)
  7. this.Task= new EventHandler(handler);
  8. else
  9. this.Task+= new EventHandler(handler);
  10. }
  11. //移除方法
  12. public void RemoveTask(EventHandler handler)
  13. {
  14. System.Delegate.Remove(this.Task, handler);
  15. }
  16. //因为委托定义为private,所以需要提供调用委托的接口
  17. public void OnTask(EventArgs e)
  18. {
  19. //7点了
  20. if (Time.Hour == 7)
  21. {
  22. if (this.Task!= null)
  23. {
  24. this.Task(this, e);
  25. }
  26. }
  27. }

如果对委托的使用仅仅是添加或移除方法,然后执行委托的调用列表,我相信用事件会更简单容易;

事件是以委托为基础,可以理解为对委托的进一步封装。

二、对事件的理解

1. 事件是将委托封装,并对外公布了订阅和取消订阅的接口

将委托private EventHandler Task;修改为事件public event EventHandler Task;而我们创建的方法AddTask和RemoveTask也需要去掉了,重新生成代码,通过反编译工具可以看到:

事件编译后生成的两个方法,与我们的示例中AddClick和RemoveClick方法类似;同时可以看到EventHandler委托,已经字段变为private的访问级别了(小锁表示私有);这样一来,事件帮我们完成了对委托的“限制“;

在客户端访问事件也只能+=(订阅)或者 -=(取消订阅)了,如果直接用“=“运算符赋值就会报错(在Control类内容还是可以的);

2. 事件使用 发布-订阅(publisher-subscriber) 模型

发布者:包含事件的类用于触发事件,而这个类称为事件的“发布者”。

通过声明委托类型的事件,将委托与事件关联。发布者对象调用这个事件,并通知订阅者对象

订阅者:其他注册该事件的类称为“订阅者”。

订阅者注册事件并提供事件处理程序(闹钟、打开窗帘、热水器烧水等),在发布者类中通过委托调用订阅者的事件处理程序。

```

public delegate void EventHandler(object sender, EventArgs e);

///



/// 控制中心

///

public class ControlCore

{

public DateTime Time { get; set; } = DateTime.Now;

///



/// 执行任务

///

public event EventHandler Task;

///



/// 触发事件

///

///

public void OnTask(EventArgs e)

{

//7点了

if (Time.Hour == 7)

{

if (this.Task != null)

{

this.Task(this, e);

}

}

}

}

  1. 订阅者类中的事件处理程序
  1. //下面的方法分别属于订阅者类中的方法,篇幅有限,没有单独声明每一个订阅者类
  2. /// <summary>
  3. /// 闹钟响起
  4. /// </summary>
  5. public static void AlarmClock(object sender, EventArgs e)
  6. {
  7. var core = (ControlCore)sender;
  8. Console.WriteLine(core.Time.Hour+"点了,起床了,亲");
  9. }
  10. /// <summary>
  11. /// 打开窗帘
  12. /// </summary>
  13. public static void OpenWindow(object sender, EventArgs e)
  14. {
  15. var core = (ControlCore)sender;
  16. Console.WriteLine(core.Time.Hour + "点了,打开窗帘");
  17. }
  18. /// <summary>
  19. /// 热水器烧水
  20. /// </summary>
  21. public static void BoilWater(object sender, EventArgs e)
  22. {
  23. var core = (ControlCore)sender;
  24. Console.WriteLine(core.Time.Hour + "点了,热水器开始烧水");
  25. }
  1. 客户端代码
  1. static void Main(string[] args)
  2. {
  3. var controlCore = new ControlCore();
  4. controlCore.Task += new EventHandler(AlarmClock);
  5. controlCore.Task += OpenWindow;
  6. controlCore.Task += BoilWater;
  7. while (true)
  8. {
  9. controlCore.OnTask(null);
  10. Console.ReadKey();
  11. }
  12. }
  1. 执行结果

7点了,起床了,亲

7点了,打开窗帘

7点了,热水器开始烧水

  1. ##### 3. 关于sender和EventArgs
  2. 在示例代码中,可以看到将ControlCore本身作为参数传递给订阅者,既然订阅了发布者的动态,那么关于发布者的某些信息或许感兴趣(比如ControlCore中的Time)。
  3. EventArgs是作为发布者信息之外的信息传递

//自定义参数类型

public class CustomEventArgs: EventArgs

{

public string Arg1 { get; set; }

public string Arg2 { get; set; }

}

  1. CustomEventArgs替换委托中的参数EventArgs```public delegate void EventHandler(object sender, CustomEventArgs e)```
  2. 那么在客户端调用时传入更多的信息

controlCore.OnTask(new ButtonEventArgs()

{

Arg1="",

Arg2=""

});

```

总结:一直想写一篇关于委托和事件的文章,但是网上已经有很多这类优秀的文章了,不乏一些佼佼者,由浅入深,从无到有的风格将知识点讲的很透彻。如果我再按照这个类型去写,实在没有意思。

所以我想,我们可不可以从已知到未知这条路径来将知识点讲明白,比如,我们知道将具有相同属性和行为的对象抽象为类型。那么我是不是可以将具有相同签名和返回类型的方法抽象为委托?再比如,我们知道属性封装了字段,并对字段的输入输出进行了限制。那么我是不是可以将委托封装,控制委托的注册或取消,这样就引出了事件。

希望可以帮助到朋友们

:.NET关于委托和事件的编码规范

  • 委托类型的名称都应该以EventHandler结束。
  • 事件的命名为 委托去掉 EventHandler之后剩余的部分。
  • 委托的原型定义:有一个void返回值,并接受两个输入参数:一个Object 类型,一个 EventArgs类型(或继承自EventArgs)。
  • 继承自EventArgs的类型应该以EventArgs结尾。

C#基本功之委托和事件的更多相关文章

  1. [ASP.NET MVC 大牛之路]02 - C#高级知识点概要(1) - 委托和事件

    在ASP.NET MVC 小牛之路系列中,前面用了一篇文章提了一下C#的一些知识点.照此,ASP.NET MVC 大牛之路系列也先给大家普及一下C#.NET中的高级知识点.每个知识点不太会过于详细,但 ...

  2. [ASP.NET 大牛之路]02 - C#高级知识点概要(1) - 委托和事件

    在ASP.NET MVC 小牛之路系列中,前面用了一篇文章提了一下C#的一些知识点.照此,ASP.NET MVC 大牛之路系列也先给大家普及一下C#.NET中的高级知识点.每个知识点不太会过于详细,但 ...

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

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

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

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

  5. [转载]C#深入分析委托与事件

    原文出处: 作者:风尘浪子 原文链接:http://www.cnblogs.com/leslies2/archive/2012/03/22/2389318.html 同类链接:http://www.c ...

  6. [转载]C#委托和事件(Delegate、Event、EventHandler、EventArgs)

    原文链接:http://blog.csdn.net/zwj7612356/article/details/8272520 14.1.委托 当要把方法作为实参传送给其他方法的形参时,形参需要使用委托.委 ...

  7. C#委托与事件

    一.在控制台下使用委托和事件 我们都知道,C#中有"接口"这个概念,所谓的"接口"就是定义一套标准,然后由实现类来具体实现其中的方法,所以说"接口,是 ...

  8. C#委托与事件的简单使用

    前言:上一篇博文从原理和定义的角度介绍了C#的委托和事件.本文通过一个简单的小故事,来说明C#委托与事件的使用方法及其方便之处. 在阅读本文之前,需要你对委托和事件的基本概念有所了解.如果你是初次接触 ...

  9. C#之委托与事件

    委托与事件 废话一堆:网上关于委托.事件的文章有很多,一千个哈姆雷特就有一千个莎士比亚,以下内容均是本人个人见解. 1. 委托 1.1 委托的使用 这一小章来学习一下怎么简单的使用委托,了解一些基本的 ...

随机推荐

  1. C++ Primer Plus 6 第一章

    一.机器语言.汇编语言.C\C++.高级语言 机器语言:机器真正识别,能在机器上运行的语言. 汇编语言:低级语言,直接操作硬件,如直接访问cpu寄存器和内存单元.不具有移植性.因为不同的平台对应的硬件 ...

  2. idea自我使用简单使用方式和出现的一些简单问题以及常用快捷键

    首先配置完Idea的简单使用步骤后,今天在使用Idea时,一直持续提示web项目404的错误提示,因为之前使用idea时,部署的是springBoot的项目,使用的是SpringBoot自带的Tomc ...

  3. 我真的知道JavaScript吗?

    JavaScript 说说JavaScript 接触JavaScript时间其实已经不短了,之前一直是半瓶酱油,东凑西凑的收集相关的知识.并没有完整系统的学习过JavaScript,觉得JavaScr ...

  4. git 忽略文件夹

    $ vim .gitignore 添加要忽略的文件或文件夹 esc + :wq 退出vim命令行

  5. QQ--基于TCP/UDP协议的通讯原理

    QQ是一个基于TCP/UDP协议的通讯软件  发送消息的时候是UDP打洞,登陆的时候使用HTTP~因为登陆服务器其实就是一个HTTP服 务器,只不过不是常用的那些,那个服务器是腾讯自行开发的!   一 ...

  6. 读Zepto源码之Stack模块

    Stack 模块为 Zepto 添加了 addSelf 和 end 方法. 读 Zepto 源码系列文章已经放到了github上,欢迎star: reading-zepto 源码版本 本文阅读的源码为 ...

  7. git学习整理(1)git clone 理解

    1.git clone 的理解 git clone默认会把远程仓库整个给clone下来 ,只能clone远程库的master分支并在本地默认创建一个master分支 ,无法clone所有分支,若想要其 ...

  8. RichEditBox 使用自定义菜单

    老周:当RichEditBox控件的上下文菜单即将弹出时,会引发ContextMenuOpening事件,我们需要处理该事件,并且将e.Handled属性设置为true,这样才能阻止默认上下文菜单的弹 ...

  9. UWP 绘制图形

    UWP图形和wpf变化不多 一般用到有椭圆.长方形.多边形.线 不过如果用的好,可以做出很漂亮的界面 一般使用画图都是使用Shape 类,Shape 类具有一个与其关联的画笔并可以呈现到屏幕,包括 L ...

  10. OpenWRT UCI命令实现无线中继

    本文主要功能主要是利用OpenWRT系统uci命令实现无线中继,主要是利用uci程序修改/etc/congfig/目录下的配置文件.实现步骤如下主要分为以下几步: 1) 安装 relayd (opkg ...