在C#中使用一个类时,分两个阶段。首先需要定义这个类,即告诉编译器这个类由什么字段和方法组成。然后(除非只使用静态方法)实例化类的一个对象。使用委托时,也需要经过这两个步骤。首先定义要使用的委托,对于委托,定义它就是告诉编译器这种类型的委托代表了哪种类型的方法,然后创建该委托的一个或多个实例。编译器在后台将创建表示该委托的一个类。
定义委托的语法如下:
访问修饰符 delegate 返回值类型委托名称(参数1, 参数1, 参数n…); //------------例如-------------------- //不返回值,也没有参数的委托类型BB public delegate voidBB(); //返回string类型值,需要两个参数的委托类型AA public delegate string AA(int a,string b); |
委托的一个要点是它们的类型安全性非常高。在定义委托时,必须给出它所代表的方法签名和返回类型等全部细节。
如果觉得不好理解可以把委托当作给方法签名和返回类型指定名称。因为其语法类似于方法的定义,但没有方法体,定义的前面要加上关键字delegate。因为定义委托基本上是定义一个新类,所以可以在定义类的任何地方定义委托,既可以在另一个类的内部定义,也可以在任何类的外部定义,还可以在命名空间中把委托定义为顶层对象。根据定义的可见性,可以在委托定义上添加一般的访问修饰符:public、private、protected等:
实际上,“定义一个委托”是指“定义一个新类”。委托实现为派生自基类System. MulticastDelegate的类,System.MulticastDelegate又派生自基类System.Delegate。C#编译器知道这个类,会使用其委托语法,因此我们不需要了解这个类的具体执行情况,这是C#与基类共同合作,使编程更易完成的另一个示例。
定义好委托后,就可以创建它的一个实例,以存储特定方法的细节。
6.3.2 使用委托
委托是一种安全地封装方法的类型,它与 C 和 C++ 中的函数指针类似。与 C 中的函数指针不同,委托是面向对象的、类型安全的和保险的。委托的类型由委托的名称定义。下面的示例声明了一个名为Del 的委托:
//声明了一个名为Del的委托,该委托可以封装一个采用字符串作为参数并返回 void 的方法。 public delegate voidDel(string message); |
构造委托对象时,通常提供委托将包装的方法的名称或使用匿名方法。实例化委托后,委托将把对它进行的方法调用传递给方法。调用方传递给委托的参数被传递给方法,来自方法的返回值(如果有)由委托返回给调用方。这被称为调用委托。可以将一个实例化的委托视为被包装的方法本身来调用该委托。例如:
using System; namespace _63demo { //声明了一个名为Del的委托,该委托可以封装一个采用字符串作为参数并返回 void 的方法。 public delegate void Del(string message); class Program { //新增一个方法, public static voidDelegateMethod(stringmessage) { System.Console.WriteLine(message); } static void Main() { //为新申明的委托实例的委托对象为DelegateMethod,这句有点绕,这样理解 //既然是委托,那么肯定会有个最终执行者,反正不要期望委托本身干事 Del handler =DelegateMethod; //使用委托,其实等同于直接使用DelegateMethod方法,因为最终是委托给这个方法的 handler("调用委托来了"); Console.ReadLine(); } } } |
小天:没觉得有什么意思,要调用方法DelegateMethod,我直接调用就行了,好像犯不着绕一大圈。你看我要显示一个消息,我首先去找到一个中介所(委托),然后将要显示的消息交给中介所,然后他来帮我显示。
老田:现在社会上形形色色的中介机构那么多,要知道存在即是合理。就上面实例来说,显示一个消息当然没有必要找中介。但如果你要做的是很复杂的事呢?下面我们再来将委托进行二次外包。有点像你喜欢上某个美女,但是你不好意思跟她说。于是你找了个做媒的人,然后你将你要给这个女孩子的定情信物和媒人一起给了一个专业说媒的方法(可以理解这个方法就是一个说媒的场景)。还是继续使用上面已经有的代码,只是新增一个方法和相应的调用语句,具体执行代码如下:
/// <summary> /// //新增一个方法,就是说媒的场景 /// </summary> /// <paramname="param1">参数(男孩信息)1</param> /// <paramname="param2">参数(所给信物)2</param> /// <paramname="callback">媒婆</param> public static voidMethodWithCallback(string param1,string param2, Del callback) { //多个字符串相加最好用这种方式,性能更高 StringBuilder say =new StringBuilder(); say.Append("有个臭P的家伙喜欢你,他的信息如下:n"); say.Append(param1); say.Append("n他还送上"); say.Append(param2); say.Append("作为定情信物"); callback(say.ToString()); } static void Main() { Del handler =DelegateMethod; string info = "名叫小天,年方二八,除了没钱和长得太帅外基本没有缺点"; //个人信息 string gift = "天轰穿趣味编程系列图书";//信物 MethodWithCallback(info, gift,handler);//将委托也作为参数传递 Console.ReadLine(); } |
执行后效果如图6-4
图6-4
小天:为什么上面的方法都是静态(static)的呢?
老田:因为这是个控制台应用程序,而控制台应用程序的入口方法Main是static的。要使这些方法能够被Main调用,当然只能是静态,这个都不清楚的话赶紧回去复习类设计那一章。
另外,将委托构造为包装实例方法时,该委托将同时引用实例和方法。除了它所包装的方法外,委托不了解实例类型,所以只要任意类型的对象中具有与委托签名相匹配的方法,委托就可以引用该对象。将委托构造为包装静态方法时,它只引用方法。比如下面示例,我们新增一个类,里面增加两个方法,然后分别交给委托:
//新增的类,里面有三个方法 public class MethodClass { public void Method1(string msg) { Console.WriteLine("第一个方法的结果是:"+msg); } public void Method2(string msg) { Console.WriteLine("第二个方法的结果是:"+msg); } } //在Main函数中使用 static void Main() { MethodClass mc = new MethodClass(); //实例化一个被调用类的实例 //注意下面三个关联的方法,他们的类实例是不同的,但是它们的方法签名是一样的 Del handler1 =mc.Method1; Del handler2 =mc.Method2; //下面这个方法是前面示例使用的方法 Del handler =DelegateMethod; //调用委托 handler1("小天"); handler2("老田"); Console.ReadLine(); } |
小天:也就是说,委托只管被委托的方法的返回类型和参数列表是符合的就可以了,并不太在意具体的类实例。难道这就是传说的“英雄不问出生”?
老田:另外,我们还可以将上面三个委托来个批发,现在将上例中三个委托实例handler1,handler2,handler3来个打包执行,代码如下:
//在Main函数中使用 static void Main() { MethodClass mc = new MethodClass(); //实例化一个被调用类的实例 Delhandler1 = mc.Method1; Del handler2 =mc.Method2; Del handler3 =DelegateMethod; Del handler4 = handler1 +handler2; //第一种打包方法 handler4 +=handler3;//将handler3添加到handler4的执行序列中 //调用委托 handler4("打包执行"); } |
6.3.3 多播委托
此时,handler4在其调用列表中包含三个方法-- Method1、Method2 和 DelegateMethod。原来的三个委托handler1、handler2和handler3 保持不变。调用 allMethodsDelegate时,将按顺序调用所有这三个方法。如果委托使用引用参数,则引用将依次传递给三个方法中的每个方法,由一个方法引起的更改对下一个方法是可见的。如果任一方法引发了异常,而在该方法内未捕获该异常,则该异常将传递给委托的调用方,并且不再对调用列表中后面的方法进行调用。如果委托具有返回值和/或输出参数,它将返回最后调用的方法的返回值和参数。若要从调用列表中移除方法,可以使用减法运算符或减法赋值运算符(“-”或“-=”)。例如:
// 从handler4中移除handler3 handler4 -=handler3; //新申明一个handler5,等于handler4移除handler2后的结果 Del handler5 = handler4 -handler2; |
小天:这个就是前面实例中讲到的多播委托吧。这个我知道了,不过我发现这么加加减减的,搞到最后,有什么办法知道到底委托序列中有多少个方法不?
老田:由于委托类型派生自 System.Delegate,所以可在委托上调用该类定义的方法和属性。例如,为了找出委托的调用列表中的方法数,您可以编写下面的代码:
//获取多路广播(简称“多播”)委托中的方法数量 handler5.GetInvocationList().GetLength(0); |
多路广播委托广泛用于事件处理中。事件源对象向已注册接收该事件的接收方对象发送事件通知。为了为事件注册,接收方创建了旨在处理事件的方法,然后为该方法创建委托并将该委托传递给事件源。事件发生时,源将调用委托。然后,委托调用接收方的事件处理方法并传送事件数据。给定事件的委托类型由事件源定义。
小天:能够对多播委托实例进行遍历不?
老田:可以的,如下例:
static void Main() { MethodClass mc = new MethodClass();//被调用方法的类实例 Del d1, d2, d3,d4;//申明多个委托实例 d1 =mc.Method1; d2 = mc.Method2; d3 = DelegateMethod; d4 = d1 + d2 +d3;//d4等于前面三个相加 //申明一个委托类型的数组,将d4中的调用列表作为值给它 Delegate[] delegates =d4.GetInvocationList(); //申明一个变量i作为计数器 int i = 0; foreach (Del d indelegates) { i++;//i递加 d("我是方法"+i.ToString());//调用当前委托 } } |
执行后效果如图6-5
图6-5
对于多播委托的更多操作我就不一一介绍了,你自己结合动态帮助去尝试吧。