委托是一种新的面向对象语言特性,委托的功能是在CLR的支持下实现的,这就意味着它并不受限于特定的编程语言,比如C#使用delegate关键字来定义委托,其他的.NET编程语言可以使用自己的方式来定义委托。
以委托作为基础,.NET构造了一个技术大厦,事件驱动、异步调用和Lambda表达式都建立于委托之上,还有许多其他的技术与委托有着密切的联系,掌握委托是探索这些技术领域的前提。
一、委托的简单使用
namespace ConsoleApplication1 { public delegate int MathOptDelegate(int x, int y); class Program { static void Main(string[] args) { MathOptDelegate mathOpt = Add; int z = mathOpt(3, 2); } private static int Add(int x, int y) { return x + y; } } }
从上例可以直观的感受到:委托可以看成是一个方法的"容器",将某一具体的方法"装入"后,就可以把它当成方法一样使用。但是不是所有的方法都可以赋值给mathOpt变量,必须满足一定的条件,定义委托类型时对方法的要求被称为方法的"签名"。
二、深入探索委托技术内幕
1.详解委托类型
用ILDASM打开编译后的项目文件,可以查看代码生成的IL指令。
不难发现使用delegate关键字定义一个委托类型时,其实是定义了一个新类MathOptDelegate,此类派生自MulticastDelegate,而MulticastDelegate又派生自Delegate。可以看到有一个构造函数和Invoke方法,继续查看Main方法,如下图所示:
查看IL_0007代码,发现调用了新生成的MathOptDelegate的构造函数,查看IL_0010代码,发现调用了MathOptDelegate的Invoke方法。所以通过委托变量间接调用方法,实际调用的是MathOptDelegate对象的Invoke方法。
通过查看IL代码,可以看到委托的真实面目,对于以下这条委托定义语句:
public delegate int MathOptDelegate(int x, int y);
C#编译器实际上是按照以下这个"代码模板"进行编译的:
public class MathOptDelegate : System.MultiDelegate { public MathOPtDelegate(Object target,Int32 methodPtr); public Int32 virtual Invoke (Int32 value1,Int32 value2); public virtual IAsyncResult BeginInvoke(Int32 value1,Int32 value2, AsyncCallback callback,Object object); public virtual Int32 EndInvoke(IAsyncResult result); }
注意MathOptDelegate类的构造函数,它接收两个参数target和methodPtr。
target:引用要调用方法的对象,如果调用的是静态方法,则target = null。
methodPtr:是一个方法指针,代表要调用的对象方法
2.委托调用列表
如果委托仅仅是方法调用的另一种方式,那何必多此一举引入"委托"这一特性?直接调用方法不更简单明了?对此问题的回答是:委托变量不仅可以引用一个方法,还可以组合多个方法并批量执行它们。
namespace ConsoleApplication1 { public delegate int MathOptDelegate(int x, int y); class Program { static void Main(string[] args) { MathOptDelegate a = Add; MathOptDelegate b = Divide; MathOptDelegate c = a + b; MathOptDelegate d = c - a; Console.ReadKey(); } private static int Add(int x, int y) { return x + y; } private static int Divide(int x, int y) { return x / y; } } }
Delegate定义了一个GetInvocationList静态方法用于获取委托调用列表(委托调用列表其实是委托对象内部所包容的一个数组,存放在数组中的元素是Delegate类型的实例,每个实例引用一个静态或实例方法),如果在代码中调用委托变量,将导致委托调用列表中的所有方法顺序执行,如果委托定义的方法有返回值,则多路委托变量的返回值为委托调用列表中最后一个方法的返回值。
但是委托创建之后,它的委托调用列表是不可更改的。使用"+"或者"-"运算符合并或分割两个委托调用列表,得到的其实是一个新的委托调用列表,可以参考字符串是不可更改的情况。
三、使用预定义的委托
.NET为了方便开发人员的使用,预先定义了几种委托,分别是Action、Function以及Predicate,使用起来都比较简单,所以就不再多介绍。
四、匿名方法和Lambda表达式
使用委托有几个步骤:
1)定义委托类型:
2)定义一个或多个符合委托类型要求的方法;
3)定义委托类型的变量;
4)将第2步定义的方法引用"挂接"到第3步定义的变量,以构建一个"委托调用列表";
5)通过委托变量"间接"调用委托调用列表;
显然,上述步骤很麻烦,能否简化?比如赋值给委托变量的方法只在这一个地方用到,其他的地方都不会调用它,为何需要单独定义成一个方法。基于上述简化开发的考虑,C#引入了"匿名方法"和"Lambda表达式"。
1.匿名方法揭秘
namespace ConsoleApplication1 { public delegate int MathOptDelegate(int x, int y); class Program { static void Main(string[] args) { MathOptDelegate a = delegate(int x, int y) { return x + y; }; Console.WriteLine(a(2, 3)); Console.ReadKey(); } } }
从示例中可以看到,"匿名方法"其实是将方法定义与委托变量赋值两个步骤合在一起,从而省掉了单独定义一个方法的麻烦。
使用ildasm查看上述代码生成的文件,会发现C#编译器自动生成了一个静态的方法:
2.奇特的Lambda表达式
上述示例中给委托变量赋值的语句
MathOptDelegate a = delegate(int x, int y){ return x + y; };
如果使用Lambda表达句,可以进一步简化: MathOptDelegate a = (x,y) => {return x + y};
所以:Lambda表达式其实就是匿名方法的进一步简化,可以用于定义一个匿名函数,交将其传递给一个委托变量。