1、什么是委托
1.1 生活中的委托
委托,汉语中的解释是把事情托付给别人或别的机构(办理)。生活中的委托挺多的,比如幼儿园,你可以把孩子委托给幼儿园,让他们帮你看管孩子;比如律师,你可以把案件委托给律师,让他帮你打官司;比如快递员,你可以把快递委托他们,让他们帮你送;这些都有一个共同点就是你可以告诉他们你要做什么,让他们去帮你做。下面我们说一下程序中的委托。
1.2 委托的概念
委托(Delegate)是C#中的一种类型,它实际上是一个能够持有对某个方法的引用的类。与其它的类不同,委托能够拥有一个签名(signature),并且它"只能持有与它的签名相匹配的方法的引用"。简单来说委托是一个类型,它能接收一个与它签名相同的方法。
2、委托详解
2.1 语法
定义委托需要使用delegate关键字,还需要返回值类型、参数列表。
delegate 返回值 委托名(可选的参数列表);
2.2 详解
上面我们介绍了委托的语法,下面我们来自己定义一个委托。
// 无参数无返回值 delegate void NoReturnNoParam();
看完有的小伙伴可能会有疑问了,这跟定义一个方法差不多啊,除了没有delegate关键字和方法体,那为什么说委托是一个类。单这么看可能看不出来啥,下面我们在反编译工具里面看一下我们上面定义的几个委托生成的IL语言。
通过上图我们看到了编译器实际上是为我们生成了一个NoReturnNoParam类,继承自System.MulticastDelegate类。它里面包含一个构造函数和BeginInvoke、EndInvoke、Invoke三个方法。现在就很明白了,虽然我们定义的时候很简单,其实编译器都给我们补全了。
3、委托的使用
上面我们知道了,委托就是一个类,那我们猜测一下是不是类怎么用,委托就可以怎么用?
3.1 自定义委托的使用
首先我们定义一个委托,再定义一个与之方法签名一致的方法。
// 无参数无返回值委托 delegate void NoReturnNoParam(); /// <summary> /// 无参数无返回值方法 /// </summary> public void NoReturnNoParamMethod() { Console.WriteLine("This is NoReturnNoParam Method ."); }
类使用的时候需要先实例化,那么委托也需要先实例化,但委托实例化的时候必须传递与方法签名相同的方法。
NoReturnNoParam noReturnNoParam = new NoReturnNoParam(new Example().NoReturnNoParamMethod); // 也可以去掉new,直接写方法 NoReturnNoParam noReturnNoParam1 = new Example().NoReturnNoParamMethod;
类实例化完了可以调用类中的实例方法,那么委托也一样。上面我们知道了委托类中包含三个方法,下面我们调用一下Invoke方法,Invoke表示执行委托,也就是执行委托传递的方法。调用的时候Invoke可以省略。
// 执行委托 noReturnNoParam.Invoke(); // 省略Invoke版,与上面等价 noReturnNoParam();
3.2 异步委托
通过BeginInvoke和EndInvoke可以实现异步编程。
3.2.1 BeginInvoke
BeginInvoke表示开始调用。执行该方法时,会开启新线程,所以不会阻塞主线程。 对于无参数委托,BeginInvoke接收两个参数,AsyncCallback表示异步委托调用结束后的回调方法,如没有回调方法则传null;第二个参数是 object类型的参数,作为额外参数,传入异步委托中,可以是任何值,一般用来标识委托。对于有参数的委托,调用BeginInvoke时需先传入方法参数,再传AsyncCallback类型和object类型参数。该方法还返回一个IAsyncResult对象。
3.2.2 EndInvoke
EndInvoke表示结束调用。该方法必须接受一个IAsyncResult对象。要注意的是,一旦调用EndEnvoke方法,就会进入阻塞模式,等待委托执行完毕,当然如果委托已经执行完毕,就可以立马取得结果。
3.2.3 使用
异步调用:
noReturnNoParam.BeginInvoke(null, null);
异步回调:
/// <summary> /// 异步回调方法 /// </summary> /// <param name="ar"></param> private void SendCallBack(IAsyncResult result) { object obj = result.AsyncState; Console.WriteLine($"执行了回调方法,AsyncState:{obj}"); // to do } AsyncCallback asyncCallBack = new AsyncCallback(this.SendCallBack); IAsyncResult asyncResult = noReturnNoParam.BeginInvoke(asyncCallBack, "obj"); noReturnNoParam.EndInvoke(asyncResult);
4、 内置委托
C#中内置了两个委托Action和Func,当然既然是委托,那用起来跟我们上面自定义的委托并没有区别。
4.1 Action
Action是一个无返回值的委托,它可以接收0-16个泛型的参数。
4.2 Func
Func是一个有返回值的委托,它可以接收0-16个泛型参数并返回一个泛型值。
4.3 用法示例
// Action Action action1 = new DelegateExample().NoReturnNoParamMethod; action1.Invoke(); Action<string> action2 = new DelegateExample().NoReturnWithParamMethod; action2.Invoke("string Param"); // Func Func<int> func1 = new DelegateExample().WithReturnNoParamMethod; int result1 = func1.Invoke(); Func<string, int> func2 = new DelegateExample().WithReturnWithParamMethod; int result2 = func2.Invoke("string Param");
5、多播委托
多播委托(MulticastDelegate)的初始化可以像普通委托一样,传入一个签名相同的实例方法。同时,多播委托重载了 += 运算符和 -= 运算符,用来向其调用列表中添加或者删除方法。调用多播委托时,方法将按照添加的顺序被依次调用。
5.1 添加方法
下面我们给多播委托添加三个方法,添加完后可通过GetInvocationList方法获取到方法列表,然后依次执行。
// 多播委托 var delegateExample = new DelegateExample(); // 添加实例方法1 MulticastDelegateTest mdTest = new DelegateExample().NoReturnNoParamMethod; // 添加实例方法2 mdTest += delegateExample.NoReturnNoParamMethod_1; // 添加静态方法 mdTest += StaticNoReturnNoParamMethod; foreach (MulticastDelegateTest item in mdTest.GetInvocationList()) { item.Invoke(); }
5.2 删除方法
既然可以添加,那肯定可以删除,我们直接把上面的+=改为-=,大家可以猜一下下面代码删除了几个方法。
// 删除实例方法1 mdTest -= new DelegateExample().NoReturnNoParamMethod; // 删除实例方法2 mdTest -= delegateExample.NoReturnNoParamMethod_1; // 删除静态方法 mdTest -= StaticNoReturnNoParamMethod; foreach (MulticastDelegateTest item in mdTest.GetInvocationList()) { item.Invoke(); }
事实证明之删除了实例方法2和静态方法。因为实例方法1添加和删除的方法对应的实例,在内存中的地址不同,所以删不掉,这点需要注意一下。
5.3 需要注意的点
-
- 多播委托的返回类型最好定义为void,有返回值时只会返回最后一个方法的返回值。
- 删除实例方法时,要删除的方法需要与添加的方法属于同一实例。
6、事件
6.1 声明事件
-
- 声明该事件的委托类型
- 基于委托定义事件
// 在类的内部声明事件,首先必须声明该事件的委托类型。 public delegate void NoReturnNoParam(); // 基于上面的委托定义事件,使用 event 关键字 public event NoReturnNoParam eventRun;
6.2 事件与委托的区别
-
- 事件是一个委托实例。
- 事件不为包容类外部的对象提供对赋值运算符,也就是事件只能出现在+=、-=左边。
- 事件确保只有包容类才能触发一个事件通知。
7、总结
本节列出委托和事件的用法,涉及代码可至GitHub下载。