C#委托的基本概念
最近在看深入理解C#,发现这是一本很不错的书,将很多C#的知识点联系了起来,更像是一本C#历史书,从C# 1一步步介绍到C# 4。
所以准备一边看,一边整理读书笔记。那么就先从委托开始。
委托是C#中一个非常重要的概念,从C# 1开始就有了委托这个核心概念,在C# 2和C# 3中委托又有了很多改进。
通过委托,我们可以将一个方法当作对象封装起来,并且在运行时,我们可以通过这个对象来完成方法的调用。
委托的使用
首先,来个简单的例子,苹果只负责设计iphone,而把组装iphone的工作委托给富士康做。
class Apple { //声明委托类型 public delegate void AssembleIphoneHandler(); public AssembleIphoneHandler AssembleIphone; public void DesignIphone() { Console.WriteLine("Design Iphone By Apple"); } } class Foxconn { //与委托类型签名相同的方法 public void AssembleIphone() { Console.WriteLine("Assemble Iphone By Foxconn"); } } class Program { static void Main(string[] args) { Apple apple = new Apple(); Foxconn foxconn = new Foxconn(); //创建委托实例 apple.AssembleIphone = new Apple.AssembleIphoneHandler(foxconn.AssembleIphone); apple.DesignIphone(); //委托实例的调用 apple.AssembleIphone(); //通过Invoke进行显示调用 //apple.AssembleIphone.Invoke(); Console.Read(); } }
从上面的例子中,可以体会一下委托的使用。使用委托需要满足4个条件:
- 声明一个委托类型
- 找到一个跟委托类型具有相同签名的方法(可以是实例方法,也可以是静态方法)
- 通过相同签名的方法来创建一个委托实例
- 通过委托实例的调用完成对方法的调用
委托类型和委托实例
当我们使用委托的时候,一定要注意这两个概念。
委托类型,是通过delegate关键字声明的一种类型,例如上面例子中的:
public delegate void AssembleIphoneHandler();
注意,"AssembleIphoneHandler"是一个委托类型,它有自己的方法,可以创建相关的实例。通过"ILSpy"可以看到"AssembleIphoneHandler"的方法以及父类信息。
委托类型的声明过程中描述了该委托类型的签名(返回类型,参数列表),这个签名就决定了那个方法可以用来创建一个改委托类型的委托实例;同时,这个签名还表示了该委托实例调用的签名。
而委托实例,就是通过委托类型进行实例化的对象,例如上面例子中的:
apple.AssembleIphone = new Apple.AssembleIphoneHandler(foxconn.AssembleIphone);
在创建委托实例的过程中,我们需要找到一个跟委托类型签名相同的方法来完成委托实例的创建。
委托的合并和删除
在前面的例子中,委托实例(apple.AssembleIphone)只对应一个操作(方法foxconn.AssembleIphone)。但是,每个委托实例都有一个操作列表,称为委托实例的调用列表(invocation list)。在System.Delegate类中,有两个静态方法Combine和Remove,通过这两个静态方法,我们就可以进行两个委托实例的调用列表的合并和删除。
接着上面的例子进行修改,这次来看看委托的合并和删除。
class Foxconn { //与委托类型签名相同的方法 public void AssembleIphone() { Console.WriteLine("Assemble Iphone By Foxconn"); } public void PackIphone() { Console.WriteLine("Pack Ipnone By Foxconn"); } public void ShipIphone() { Console.WriteLine("Ship Iphone By Foxconn"); } } class Program { static void Main(string[] args) { Apple apple = new Apple(); Foxconn foxconn = new Foxconn(); //创建委托实例 apple.AssembleIphone = new Apple.AssembleIphoneHandler(foxconn.AssembleIphone); //apple.AssembleIphone += new Apple.AssembleIphoneHandler(foxconn.PackIphone); apple.AssembleIphone = (Apple.AssembleIphoneHandler)Delegate.Combine(apple.AssembleIphone, new Apple.AssembleIphoneHandler(foxconn.PackIphone)); apple.AssembleIphone += new Apple.AssembleIphoneHandler(foxconn.ShipIphone); apple.DesignIphone(); //委托实例的调用 apple.AssembleIphone(); //通过Invoke进行显示调用 //apple.AssembleIphone.Invoke(); Console.Read(); } }
这次,我们在Foxconn类中加了打包和运输的方法,这样,可以通过Combine方法将组装、打包和运输三个操作合并到委托实例apple.AssembleIphone的调用列表中。
当我们调用委托实例的时候,委托实例的调用列表中的所有操作会依次被执行。
注意,一般在代码中,很少直接使用Combine和Remove方法的显式调用,而是通过"+="和"-="操作符来实现。
委托是不易变的
这里有一点要提的是,委托是不易变的,一旦创建了一个委托实例后,这个实例的所有内容都不能被改变了(就像string一样,string也是不易变的)。
所以说Combine和Remove都没有改变委托实例,都是新建了一个委托实例。
委托调用列表
在使用调用列表的时候,有些关键点需要注意一下,假如说一个委托实例的调用列表为[methodA, methodB, methodC]。那么当我们调用委托实例的时候,methodA, methodB, methodC会依次被执行。
- 如果上面A-C三个方法都有返回值,我们只能得到最后一个操作的返回值,其他的返回值都将被忽略。
- 如果调用列表中的一个操作有异常,那么所有的下游操作都不会被执行。
举例,我们在PackIphone方法中加入一个异常,那么委托列表中的ShipIphone操作将不会被执行到:
class Foxconn { //与委托类型签名相同的方法 public void AssembleIphone() { Console.WriteLine("Assemble Iphone By Foxconn"); } public void PackIphone() { throw new NotImplementedException(); } public void ShipIphone() { Console.WriteLine("Ship Iphone By Foxconn"); } } class Program { static void Main(string[] args) { Apple apple = new Apple(); Foxconn foxconn = new Foxconn(); //创建委托实例 apple.AssembleIphone = new Apple.AssembleIphoneHandler(foxconn.AssembleIphone); //apple.AssembleIphone += new Apple.AssembleIphoneHandler(foxconn.PackIphone); apple.AssembleIphone = (Apple.AssembleIphoneHandler)Delegate.Combine(apple.AssembleIphone, new Apple.AssembleIphoneHandler(foxconn.PackIphone)); apple.AssembleIphone += new Apple.AssembleIphoneHandler(foxconn.ShipIphone); apple.DesignIphone(); //委托实例的调用 try { apple.AssembleIphone(); } catch { Console.WriteLine("an exception happened"); } Console.Read(); } }
GetInvocationList
对于上面两个问题,我们可以通过委托实例的GetInvocationList()方法,通过这个方法可以得到调用列表中的所有操作。
这样,就可以显示调用委托来进行异常处理或者返回值的保存。
foreach (Apple.AssembleIphoneHandler method in apple.AssembleIphone.GetInvocationList()) { try { method(); } catch { Console.WriteLine("an exception happened"); } }
总结
本文介绍了委托的基本概念,以及委托类型和委托实例的区别。
委托本质上是一个派生自System.MulticastDelegate的类,我们可以通过特定的(与委托类型签名相同)的方法创建委托实例,通过委托是,可以间接完成某些操作。
同时,可以通过Combine和Remove操作来进行委托实例的调用列表的合并和删除。