定义:委托是一种引用类型,表示对具有特定参数列表和返回类型的方法的引用。 在实例化委托时,你可以将其实例与任何具有兼容签名和返回类型的方法相关联
目的:方法声明和方法实现的分离,使得程序更容易扩展
先不解释定义,看一段代码
- public void Method1(object obj)
- {
- //内部可以访问obj的成员
- }
这是随便写的一个方法,没有实际意义,但是,根据我们已掌握的关于类型的基础知识,应该明白,这里的obj(引用类型)作为形参,存放的是对象的引用,既然获取到了对象的引用,那么我们可以在run方法内部对obj的成员进行访问(一段废话)。好了,现在我要问一个问题:为什么要将obj作为参数
问题先慢慢想着,我们再次看一下委托的定义,"是引用类型,对方法的引用",看下面的代码
- public void Method2(delegate del)
- {
- //内部可以访问del的什么?
- //只能执行方法
- del();
- }
delegate 作为一种引用类型,引用的是个方法,我们能对方法做什么,只能执行方法。
下面我们回答刚才的问题,obj作为参数(类型声明),将类型的声明和类型的实例分离。当然这样做的目的是为了封装变化,所有类型都可以作为实参来使用Method1,因为Object是基类,当然也可以将Object换成其他接口类型,只要实现了该接口的类型都可以作为Method1的实参。
虽然Method1和Method2参数类型不一样,但是目的是一致的,委托是将方法的声明和实现分离。
下面看一个网上使用广泛的例子
- 示例1
- //定义委托,与任何具有兼容签名和返回类型的方法相关联
- public delegate void GreetingDelegate(string name);
- class Program
- {
- private static void EnglishGreeting(string name)
- {
- Console.WriteLine("Morning, " + name);
- }
- private static void ChineseGreeting(string name)
- {
- Console.WriteLine("早上好, " + name);
- }
- //将委托类型GreetingDelegate作为形参声明
- private static void GreetPeople(string name, GreetingDelegate MakeGreeting)
- {
- //这里可以完成其他的业务逻辑
- //然后调用委托
- MakeGreeting(name);
- }
- static void Main(string[] args)
- {
- GreetPeople("hanmeimei", EnglishGreeting);//使用静态方法初始化委托
- GreetPeople("韩梅梅", new Program().ChineseGreeting);//使用实例方法初始化委托
- Console.ReadKey();
- }
- }
委托类型GreetingDelegate作为形参声明,只要是具有兼容签名和返回类型的方法都可以作为GreetPeople方法的实参。这样一来,GreetPeople方法不仅可以通过中文和英文问好了,所有与委托类型GreetingDelegate相关联的语言(方法)都可以问好了,程序更容易扩展了。
既然委托是一种类型,应该包含类型的成员,我们将代码编译完成后,借助反编译工具看下,编译后的样子:
下面这两种方式是等同的:
- MakeGreeting(name);
- MakeGreeting.Invoke(name);
而BeginInvoke和EndInvoke是属于异步调用的范畴,我们稍后再说。
从示例1中可以看到分别显示调用了静态方法和实例方法初始化了委托,但是对于那些只使用一次的方法,就没有必要创建具名方法了。C#2.0提出了使用匿名方法代替具名方法的解决方案
- GreetingDelegate MakeGreeting = delegate (string name)
- {
- Console.WriteLine("早上好, " + name);
- };
- GreetPeople("韩梅梅", MakeGreeting);
匿名方法仍然比较繁琐,C#3.0引入了Lambda表达式
- //去掉delegate关键字并添加 =>运算符
- GreetingDelegate MakeGreeting = (string name) =>
- {
- Console.WriteLine("早上好, " + name);
- };
- GreetPeople("韩梅梅", MakeGreeting);
- //根据委托的参数类型推断,string类型也去掉了
- GreetingDelegate MakeGreeting = name =>
- {
- Console.WriteLine("早上好, " + name);
- };
- GreetPeople("韩梅梅", MakeGreeting);
- GreetingDelegate MakeGreeting1 = (int name) =>
- {
- Console.WriteLine("早上好, " + name);
- };
常用泛型委托
关于泛型委托的协变和逆变可以阅读这篇文章《C#基本功之泛型》
有了常用泛型委托,我们就不需要自己定义GreetingDelegate委托类型了,对委托的使用进一步的简化了。
- //用泛型委托声明形参,Func于此类似,只不过有返回值而已
- private static void GreetPeople(string name,Action<string> action)
- {
- //这里可以完成其他的业务逻辑
- //然后调用委托
- action.Invoke(name);
- }
- //调用
- GreetPeople("韩梅梅", name => Console.WriteLine("早上好, " + name))
到目前为止,我们将方法的变化抽象,并用委托封装,实现了委托的简单使用。但委托还有很大的用处。
委托是类的成员,我们看一个作为类的成员使用的例子:现在生活中智能设备越来越普及,以前需要自己动手拉开窗帘、打开热水器等等,现在只需要设定场景,利用智能设备就可以完成这些操作。
当时间定格为早上7点的时候,闹钟想起,窗帘自动打开,热水器开始烧水,加湿器关闭.....
- //先不用管EventArgs参数,object 类型的sender,可以理解为任何类型都可以传递
- public delegate void EventHandler(object sender, EventArgs e);
- /// <summary>
- /// 控制中心
- /// </summary>
- public class ControlCore {
- public DateTime Time {
- get;
- set;
- } = DateTime.Now;
- /// <summary>
- /// 执行任务
- /// </summary>
- public event EventHandler Task;
- }
- static void Main(string[] args) {
- var controlCore = new ControlCore();
- controlCore.Task = new EventHandler(AlarmClock);
- //怎么操作委托?
- //添加、移除
- controlCore.Task += ....;controlCore.Task -= ....;
- //或者直接覆盖掉
- controlCore.Task = ....;controlCore.Task(null, null);
- }
- /// <summary>
- /// 闹钟
- /// </summary>
- public static void AlarmClock(object sender, EventArgs e) {
- Console.WriteLine("起床了,亲");
- }
作为类的成员时,要怎么操作委托?我们都知道委托还可以添加或移除方法,所以我们不仅可以直接调用委托,还可以添加、移除或者直接覆盖委托,对委托的操作没有任何限制。如此一来,破坏了类的封装性。我们希望对委托有一些限制,就像用属性去限制字段的输入输出一样;
- //将委托的访问级别改为private
- private EventHandler Task;
- //为委托添加方法
- public void AddTask(EventHandler handler)
- {
- if (this.Task== null)
- this.Task= new EventHandler(handler);
- else
- this.Task+= new EventHandler(handler);
- }
- //移除方法
- public void RemoveTask(EventHandler handler)
- {
- System.Delegate.Remove(this.Task, handler);
- }
- //因为委托定义为private,所以需要提供调用委托的接口
- public void OnTask(EventArgs e)
- {
- //7点了
- if (Time.Hour == 7)
- {
- if (this.Task!= null)
- {
- this.Task(this, e);
- }
- }
- }
如果对委托的使用仅仅是添加或移除方法,然后执行委托的调用列表,我相信用事件会更简单容易;
事件是以委托为基础,可以理解为对委托的进一步封装。
将委托
修改为事件
- private EventHandler Task;
而我们创建的方法AddTask和RemoveTask也需要去掉了,重新生成代码,通过反编译工具可以看到:
- public event EventHandler Task;
事件编译后生成的两个方法,与我们的示例中AddClick和RemoveClick方法类似;同时可以看到EventHandler委托,已经字段变为private的访问级别了(小锁表示私有);这样一来,事件帮我们完成了对委托的“限制“;
在客户端访问事件也只能+=(订阅)或者 -=(取消订阅)了,如果直接用“=“运算符赋值就会报错(在Control类内容还是可以的);
发布者:包含事件的类用于触发事件,而这个类称为事件的“发布者”。
通过声明委托类型的事件,将委托与事件关联。发布者对象调用这个事件,并通知订阅者对象
订阅者:其他注册该事件的类称为“订阅者”。
订阅者注册事件并提供事件处理程序(闹钟、打开窗帘、热水器烧水等),在发布者类中通过委托调用订阅者的事件处理程序。
- public delegate void EventHandler(object sender, EventArgs e);
- /// <summary>
- /// 控制中心
- /// </summary>
- public class ControlCore {
- public DateTime Time {
- get;
- set;
- } = DateTime.Now;
- /// <summary>
- /// 执行任务
- /// </summary>
- public event EventHandler Task;
- /// <summary>
- /// 触发事件
- /// </summary>
- /// <param name="e"></param>
- public void OnTask(EventArgs e) {
- //7点了
- if (Time.Hour == 7) {
- if (this.Task != null) {
- this.Task(this, e);
- }
- }
- }
- }
订阅者类中的事件处理程序
- //下面的方法分别属于订阅者类中的方法,篇幅有限,没有单独声明每一个订阅者类
- /// <summary>
- /// 闹钟响起
- /// </summary>
- public static void AlarmClock(object sender, EventArgs e)
- {
- var core = (ControlCore)sender;
- Console.WriteLine(core.Time.Hour+"点了,起床了,亲");
- }
- /// <summary>
- /// 打开窗帘
- /// </summary>
- public static void OpenWindow(object sender, EventArgs e)
- {
- var core = (ControlCore)sender;
- Console.WriteLine(core.Time.Hour + "点了,打开窗帘");
- }
- /// <summary>
- /// 热水器烧水
- /// </summary>
- public static void BoilWater(object sender, EventArgs e)
- {
- var core = (ControlCore)sender;
- Console.WriteLine(core.Time.Hour + "点了,热水器开始烧水");
- }
客户端代码
- static void Main(string[] args)
- {
- var controlCore = new ControlCore();
- controlCore.Task += new EventHandler(AlarmClock);
- controlCore.Task += OpenWindow;
- controlCore.Task += BoilWater;
- while (true)
- {
- controlCore.OnTask(null);
- Console.ReadKey();
- }
- }
执行结果
- 7点了,起床了,亲
- 7点了,打开窗帘
- 7点了,热水器开始烧水
在示例代码中,可以看到将ControlCore本身作为参数传递给订阅者,既然订阅了发布者的动态,那么关于发布者的某些信息或许感兴趣(比如ControlCore中的Time)。
而EventArgs是作为发布者信息之外的信息传递
- //自定义参数类型
- public class CustomEventArgs: EventArgs
- {
- public string Arg1 { get; set; }
- public string Arg2 { get; set; }
- }
用CustomEventArgs替换委托中的参数EventArgs。
那么在客户端调用时传入更多的信息
- public delegate void EventHandler(object sender, CustomEventArgs e)
- controlCore.OnTask(new ButtonEventArgs()
- {
- Arg1="",
- Arg2=""
- });
总结:一直想写一篇关于委托和事件的文章,但是网上已经有很多这类优秀的文章了,不乏一些佼佼者,由浅入深,从无到有的风格将知识点讲的很透彻。如果我再按照这个类型去写,实在没有意思。
所以我想,我们可不可以从已知到未知这条路径来将知识点讲明白,比如,我们知道将具有相同属性和行为的对象抽象为类型。那么我是不是可以将具有相同签名和返回类型的方法抽象为委托?再比如,我们知道属性封装了字段,并对字段的输入输出进行了限制。那么我是不是可以将委托封装,控制委托的注册或取消,这样就引出了事件。
希望可以帮助到朋友们
注:.NET关于委托和事件的编码规范
来源: http://www.cnblogs.com/gzb8/p/7815476.html