大话C#之委托
开篇先来扯下淡,上篇博客LZ在结尾说这篇博客会来说说C#中的事件。但是当LZ看完事件之后发现事件是以委托为基础来实现的,于是LZ就自作主张地在这篇博客中先来说说委托,还烦请各位看官见谅!!!另外关于委托推荐一篇Jimmy Zhang写的关于委托的博客(C# 中的委托和事件),叙述非常有条理,可见子阳兄的文笔不凡。
博客结构
- 加工厂问题
- 委托来提高加工厂效率
- 委托的更多用法
- 委托到底是什么
加工厂问题
假设现在我们开了一个电子设备外包工厂(就像里面有很多人跳楼的那某某康),专门负责为国际上的大公司代工生产电子设备产品。某天,加工厂收到了来自美国苹果公司的订单,苹果公司委托加工厂为他们生产一批iPhone电话机,于是加工厂就像下面这么干,效果不错还挺好,苹果公司很满意。
1 static void Main(string[] args) 2 { 3 DigitalFactory factory = new DigitalFactory(); 4 factory.MakeiPhone(200); 5 } 6 7 //摘要: 8 // 加工厂类 9 public class DigitalFactory 10 { 11 // 生产iPhone 12 public void MakeiPhone(Int32 Number) 13 { 14 Console.WriteLine("" + Number + "台iPhone已经成功生产出来了......"); 15 Console.ReadKey(); 16 } 17 } 18 输出: 19 200台iPhone已经成功生产出来了......
后来呢,苹果公司觉得加工厂生产的iPhone质量很好,于是又委托加工厂为他们生产一批iPad平板电脑,于是加工厂又像下面这么干了。
1 static void Main(string[] args) 2 { 3 DigitalFactory factory = new DigitalFactory(); 4 factory.MakeiPhone(200); 5 factory.MakeiPad(200); 6 } 7 8 //摘要: 9 // 加工厂类 10 public class DigitalFactory 11 { 12 // 生产iPhone 13 public void MakeiPhone(Int32 Number) 14 { 15 Console.WriteLine("" + Number + "台iPhone已经成功生产出来了......"); 16 Console.Read(); 17 } 18 19 // 生产iPad 20 public void MakeiPad(Int32 Number) 21 { 22 Console.WriteLine("" + Number + "台iPad已经成功生产出来了......"); 23 Console.Read(); 24 } 25 } 26 输出: 27 200台iPhone已经成功生产出来了...... 28 200台iPad已经成功生产出来了......
这次苹果公司更满意,又继续给了代工生产iMac和iPod的两个订单,于是加工厂又傻乎乎地像下面这么干了。
1 static void Main(string[] args) 2 { 3 DigitalFactory factory = new DigitalFactory(); 4 factory.MakeiPhone(200); 5 factory.MakeiPad(200); 6 factory.MakeiMac(200); 7 factory.MakeiPod(200); 8 } 9 10 //摘要: 11 // 加工厂类 12 public class DigitalFactory 13 { 14 // 生产iPhone 15 public void MakeiPhone(Int32 Number) 16 { 17 Console.WriteLine("" + Number + "台iPhone已经成功生产出来了......"); 18 Console.Read(); 19 } 20 21 // 生产iPad 22 public void MakeiPad(Int32 Number) 23 { 24 Console.WriteLine("" + Number + "台iPad已经成功生产出来了......"); 25 Console.Read(); 26 } 27 28 // 生产iMac 29 public void MakeiMac(Int32 Number) 30 { 31 Console.WriteLine("" + Number + "台iMac已经成功生产出来了......"); 32 Console.Read(); 33 } 34 35 // 生产iPod 36 public void MakeiPod(Int32 Number) 37 { 38 Console.WriteLine("" + Number + "台iPod已经成功生产出来了......"); 39 Console.Read(); 40 } 41 } 42 输出: 43 200台iPhone已经成功生产出来了...... 44 200台iPad已经成功生产出来了...... 45 200台iMac已经成功生产出来了...... 46 200台iPod已经成功生产出来了......
到现在加工厂才发现,随着订单越来越多,每代工生产一种产品就得新定义一个方法,那样效率太低了已经忙不过来了。所以,加工厂采用了一种新的生产模式来提高效率。
委托来提高加工厂效率
前面说到加工厂发现傻傻地为每一种代工产品提供一个发放的效率实在是太低下了,于是采用了一种新的模式。没错,大家可能都已经猜到了,这种新模式就是委托,让我们一起来看一下这新模式是如何提高效率的。
1 //摘要: 2 // 定义一个委托类型 3 public delegate void Dele(Int32 Number); 4 5 static void Main(string[] args) 6 { 7 DigitalFactory factory = new DigitalFactory(); 8 //将生产iPhone的方法传递给生产电子设备的方法 9 factory.MakeDigitals(new Apple().MakeiPhone, 200); 10 //将生产iPad的方法传递给生产电子设备的方法 11 factory.MakeDigitals(new Apple().MakeiPad, 200); 12 //将生产iMac的方法传递给生产电子设备的方法 13 factory.MakeDigitals(new Apple().MakeiMac, 200); 14 //将生产iPod的方法传递给生产电子设备的方法 15 factory.MakeDigitals(new Apple().MakeiPod, 200); 16 } 17 18 //摘要: 19 // 加工厂类 20 public class DigitalFactory 21 { 22 //定义一个通用的生产设备方法,接受一个委托变量和一个设备预生产数量作为参数 23 public void MakeDigitals(Dele dele, Int32 Number) 24 { 25 //判断委托对象是否为空,非空才执行 26 if (dele != null) 27 { 28 dele(Number); 29 } 30 } 31 } 32 33 //摘要: 34 // 苹果公司类 35 public class Apple 36 { 37 // 生产iPhone 38 public void MakeiPhone(Int32 Number) 39 { 40 Console.WriteLine("" + Number + "台iPhone已经成功生产出来了......"); 41 Console.Read(); 42 } 43 44 // 生产iPad 45 public void MakeiPad(Int32 Number) 46 { 47 Console.WriteLine("" + Number + "台iPad已经成功生产出来了......"); 48 Console.Read(); 49 } 50 51 // 生产iMac 52 public void MakeiMac(Int32 Number) 53 { 54 Console.WriteLine("" + Number + "台iMac已经成功生产出来了......"); 55 Console.Read(); 56 } 57 58 // 生产iPod 59 public void MakeiPod(Int32 Number) 60 { 61 Console.WriteLine("" + Number + "台iPod已经成功生产出来了......"); 62 Console.Read(); 63 } 64 } 65 输出: 66 200台iPhone已经成功生产出来了...... 67 200台iPad已经成功生产出来了...... 68 200台iMac已经成功生产出来了...... 69 200台iPod已经成功生产出来了......
加工厂现在定义了一个委托(上面代码中的Dele),要求苹果公司就是按照委托的要求将产品的设计、工艺、生产流程等和生产产品所有有关的细节全整理出来。加工厂现在就只有一个通用的产品生产方法(上面代码中的MakeDigitals),加工厂不再关心电子设备的其他细节,只要按照苹果公司给的生产设备的方法(上面代码中的MakeiPhone,MakeiPad等)生产出产品即可。这样,加工厂的效率是不是就提高多了?
委托往简单的方面说,就是“把方法作为方法的参数传递给方法”。这句话是不是很绕口?意思就是假如方法A接受一个委托类型的参数,其他只要符合委托类型签名的方法就都可以当做参数传递给方法A。在上面的代码中,委托Dele签名指定的方法要获取一个Int32类型的参数,并且返回值为void。苹果公司定义的四个方法MakeiPhone,MakeiPad,MakeiMac和MakeiPod的签名都符合委托类型Dele指定的方法的签名,所以这四个方法才能被传递给接受Dele类型作为参数的MakeDigitals方法。然后在MakieDigitals方法内部回调当做参数传递进来的方法,执行生产电子设备的逻辑。
让我们来看一看c#中委托的定义:
1 //摘要: 2 // 定义一个委托类型 3 public delegate void Dele(Int32 Number);
C#通过delegate关键字来声明委托,同时要指定委托的返回类型(此处为void)和参数(此处为Int32类型的Number),这样委托就声明好了。定义好的委托就相当于一个“类型”。下面就来看一下接受委托类型作为参数的方法的定义:
1 //定义一个通用的生产设备方法,接受一个委托变量和一个设备预生产数量作为参数 2 public void MakeDigitals(Dele dele, Int32 Number) 3 { 4 //判断委托对象是否为空,非空才执行 5 if (dele != null) 6 { 7 dele(Number); 8 } 9 }
MakeDigitals方法接受一个Dele类型的委托和一个32位整数作为参数(把Dele想象成String就很好理解了,委托让我们也过了一个定义“类型”的瘾,哈哈)。只要符合Dele指定签名的方法就都可以作为“参数”传递给MakeDigitals方法了。下面就是调用MakeDigitals方法的代码:
1 DigitalFactory factory = new DigitalFactory(); 2 //将生产iPhone的方法传递给生产电子设备的方法 3 factory.MakeDigitals(new Apple().MakeiPhone, 200);
看到这是不是觉得委托很简单呢?其实委托还可以帮助我们完成更多的事!
委托的更多用法
在加工厂的代码中我们只是使用委托回调了实例方法,其实委托还可以回调静态方法,还可以通过委托链一次性调用多个方法。下面我们就还是通过加工厂的代码来试探一下委托这位兄台都有哪些本事!
- 委托调用静态方法
加工厂的例子中我们全部回调的是实例方法,下面我们还是通过加工厂来调用一下静态方法:
1 //摘要: 2 // 定义一个委托类型 3 public delegate void Dele(Int32 Number); 4 5 static void Main(string[] args) 6 { 7 DigitalFactory factory = new DigitalFactory(); 8 //将生产iPhone的方法传递给生产电子设备的方法,此时MakeiPhone为静态方法 9 factory.MakeDigitals(Apple.MakeiPhone, 200); 10 } 11 12 //摘要: 13 // 加工厂类 14 public class DigitalFactory 15 { 16 //定义一个通用的生产设备方法,接受一个委托变量和一个设备预生产数量作为参数 17 public void MakeDigitals(Dele dele, Int32 Number) 18 { 19 //判断委托对象是否为空,非空才执行 20 if (dele != null) 21 { 22 dele(Number); 23 } 24 } 25 } 26 27 //摘要: 28 // 苹果公司类 29 public class Apple 30 { 31 // 生产iPhone,静态方法 32 public static void MakeiPhone(Int32 Number) 33 { 34 Console.WriteLine("" + Number + "台iPhone已经成功生产出来了......"); 35 Console.Read(); 36 } 37 } 38 输出: 39 200台iPhone已经成功生产出来了......
通过委托回调静态方法和回调实例方法类似,按照传统的静态方法调用方式调用就行了。
- 通过委托链调用多个方法
CLR通过委托链在很大程度上帮助我们减少了新建委托类型对象的数量,只要是符合委托类型签名规则的方法,都可以加入到委托类型实例的委托链中。CLR会在调用委托类型实例的代码处循环去调用委托链 中的方法,我们一起来看一下委托链的使用:
1 //省略Dele、Apple和DigitalFactory的定义代码 2 3 static void Main(string[] args) 4 { 5 DigitalFactory factory = new DigitalFactory(); 6 //新建一个Dele类型的委托对象,并且把MakeiPhone方法包装到委托对象里面 7 Dele del1 = new Dele(Apple.MakeiPhone); 8 //将MakeiPad方法加入委托对象的委托链 9 del1 += new Apple().MakeiPad; 10 //将MakeiMac方法加入委托对象的委托链 11 del1 += new Apple().MakeiMac; 12 //将MakeiPod方法加入委托对象的委托链 13 del1 += new Apple().MakeiPod; 14 factory.MakeDigitals(del1, 200); 15 } 16 输出: 17 200台iPhone已经成功生产出来了...... 18 200台iPad已经成功生产出来了...... 19 200台iMac已经成功生产出来了...... 20 200台iPod已经成功生产出来了......
可以看到在上面的代码中,我们新建了一个Dele类型的委托对象(新建时包装了对Apple.MakeiPhone方法的引用),然后通过 += 操作符将其余三个方法的引用加入到了del1的委托链中,最后通过和加工厂代码相同的方式调用委托,输出了同样的结果。
- 委托与Lambda表达式
将符合委托类型方法签名的方法定义好,然后再通过委托回调这些方法固然很有用,绝大多数时候我们也这么干,效果也还不错。但是在某些情况下定义的方法就被回调一两次,这太对不起我们辛辛苦苦地定 义方法了,Microsoft于是急我们之所急为我们提供了简便方法,真是大好人呐!!!下面我们来看看大好人是怎么对我们好的:
1 //省略Dele、Apple和DigitalFactory的定义代码 2 3 static void Main(string[] args) 4 { 5 DigitalFactory factory = new DigitalFactory(); 6 factory.MakeDigitals(obj => 7 { 8 Console.WriteLine(obj + "台iPhone已经成功生产出来了......"); 9 Console.ReadKey(); 10 }, 200); 11 } 12 输出: 13 200台iPhone已经成功生产出来了......
上面我们就没定义MakeiPhone()方法,把它的实现内联进了代码里面。=>的左边就是方法需要的参数,如果方法需要多个参数就需要用括号括起来并且用逗号隔开。=>右边是方法的主体,多行语句也需要用大括号括起来。如果委托预期一个返回值,直接在内联代码里面加入return语句就行了。
另外,在使用Lambda表达式内联进代码的方式时,Lambda表达式还可以访问类的内部成员,像下面这样:
1 //省略Dele、Apple和DigitalFactory的定义代码 2 3 static void Main(string[] args) 4 { 5 String LambdaDesc = "这是使用Lambda表达式实现的委托调用:"; 6 DigitalFactory factory = new DigitalFactory(); 7 factory.MakeDigitals(obj => 8 { 9 Console.Write(LambdaDesc); 10 Console.WriteLine(obj + "台iPhone已经成功生产出来了......"); 11 Console.ReadKey(); 12 }, 200); 13 } 14 输出: 15 这是使用Lambda表达式实现的委托调用:200台iPhone已经成功生产出来了......
在Lambda表达式的内部,我们访问了属于Main方法的LambdaDesc变量。值得注意的是此时的MakeDigitals是实例方法,如果MakeDigitals是静态方法那么就只能访问静态成员,而不能访问此处的LambdaDesc。
另外:FCL已经为我们定义了大部分一般情况下需要的委托类型,例如:System.Func<out TResult>、System.Predicate<in T>、System.Action<>等等。这些委托类型能够满足绝大部分我们日常编码中的需要。
委托到底是什么
前面我们看过了委托的各种用法,那么委托为什么能够回调方法?委托到底是怎么实现的呢?下面我们就来看看委托究竟是个什么东西!
首先,我们使用ILDasm.exe来查看生成的程序集,看看编译器生成了什么IL,编译器生成的IL如下:
通过上面的图片我们看到,编译器把Dele类型的委托编译成了一个类,这个类继承自System.MulticastDelete,同时它还有一个无返回值的构造函数,以及BeginInvoke、EndInvoke和Invoke三个方法。到这里就真相大白了,其实委托是一个类。它的完整定义就像下面这样:
1 public class Dele : System.MulticastDelegate 2 { 3 //构造器 4 public Dele(Object object,IntPtr method); 5 6 public virtual void Invoke(Int32 value); 7 8 //以下两个方法实现了对方法的回调 9 public virtual IAsyncResult BeginInvoke(Int32 value,AsyncCallback callback,Object object); 10 11 public virtual IAsyncResult EndInvoke(IAsyncResult result); 12 }
所有的委托类型都派生自System.MulticastDelegate,System.MulticastDelegate又派生自System.Delegate,而System.Delegate最终派生自所有类型的基类:System.Object。因为委托是类,所以只要能够定义类的地方就都可以定义委托,下面我们先来看一下System.MulticastDelegate中三个非常重要的非公共字段_target、_methodPtr和_invocationList:
- _target:从定义就可以看出,这个字段引用的是回调方法要操作的对象,也就是定义回调方法的类型的实例,在加工厂的代码里面,_target引用的就是Apple的实例。值得注意的是如果委托对象包装的是一个静态方法,那么_target就为null。
- _methodPtr:代表的是一个整数值,CLR用这个整数值来标识要回调的方法。
- _invocationList:引用的是一个委托数组,当委托对象只包装了一个方法时,该字段的值为null。如果委托对象包装了一组方法,该字段就引用一个委托数组,就是我们前面使用的委托链的实现。
因为上面的三个字段都是MulticastDelegate类的非公共字段,所以是不能访问的,但是我们可以访问Delegate的Target和Method属性,功能和_target以及_methodPtr一样。我们还是通过加工厂的代码来看一下:
1 public class DigitalFactory 2 { 3 //定义一个通用的生产设备方法,接受一个委托变量和一个设备预生产数量作为参数 4 public void MakeDigitals(Dele dele, Int32 Number) 5 { 6 //判断委托对象是否为空,非空才执行 7 if (dele != null) 8 { 9 dele.Invoke(Number); 10 Console.WriteLine("Target:" + dele.Target + ",Method:" + dele.Method.Name); 11 Console.ReadKey(); 12 } 13 } 14 } 15 输出: 16 200台iPhone已经成功生产出来了...... 17 Target:AllChapters.Program+Apple,Method:MakeiPhone