《.NET技术》1ofpage第一部分Microsoft.NET框架基本原理第二部分类型与通用语言运行时第三部分类型设计第四部分基本类型第五部分类型管理《.NET技术》2ofpage第四部分基本类型•文本处理•枚举类型与位标记•数组•接口•定制特性•委托《.NET技术》3ofpage第17章委托、事件驱动《.NET技术》4ofpage17.1委托•委托是一种新的面向对象语言特性。•基于委托,开发事件驱动的应用程序变得非常简便。通过灵活地使用委托,.NETFramework设计出了一套异步编程框架,使程序员很方便地开发出具有多线程特性的应用程序。•在.NETFramework基类库中,大量地使用了委托(delegate)。委托到底是什么?《.NET技术》5ofpage我们都很熟悉常用的数据类型(如int)的使用方法:先定义一个变量,然后再给其赋值,如下所示:inti;//定义变量i=100;//给变量赋值•委托(delegate)也可以看成是一种数据类型,可以用于定义变量。但它是一种特殊的数据类型,它所定义的变量能接收的数值只能是一个函数,更确切地说,委托类型的变量可以接收一个函数的地址,很类似于C/C++语言的函数指针。•简单地说:委托变量可看成是一种类型安全的函数指针,它只能接收符合其要求的函数引用。17.1.1理解委托的概念《.NET技术》6ofpagepublicclassMathOpt{publicintAdd(intargument1,intargument2){returnargument1+argument2;}}publicdelegateintMathOptDelegate(intvalue1,intvalue2);classProgram{staticvoidMain(string[]args){MathOptDelegateoppDel;MathOptobj=newMathOpt();oppDel=obj.Add;Console.WriteLine(oppDel(1,2));//输出3//Console.ReadKey();}}示例1:《.NET技术》7ofpage•从上述示例中可得到一个直观的印象:委托可以看成是一个函数的“容器”,将某一具体的函数“装入”后,就可以把它当成函数一样使用。•其实,委托是一个派生自Delegate的类,但从使用角度理解为函数“容器”也是可以的。•那么,是不是所有的函数都可以赋值给委托类型MathOptDelegate的变量oppDel呢?注意MathOptDelegate的定义语句:publicdelegateintMathOptDelegate(intvalue1,intvalue2);它规定了委托类型的变量只能接收这样的函数:拥有两个int类型的参数,并且返回值类型也是int。只要是满足上述要求的函数,不管名字如何,也不管是静态的还是实例的,都可以传给委托类型的变量oppDel,并通过oppDel来“间接地”调用它们。《.NET技术》8ofpage•定义委托类型时对函数的要求被称为函数的“签名(Signature)”。•函数的签名规定了函数的参数数目和类型,以及函数的返回值,体现了函数的本质特征。•每一个委托都确定了一个函数的签名。拥有不同签名的函数不能赋值给同一类型的委托变量。《.NET技术》9ofpage13.1.2委托的组合与分解•委托不仅可以代表一个函数,还可以组合“一堆”的函数,然后批量执行它们。下面示例2,展示了委托变量之间的组合与分解。delegatevoidMyDelegate(strings);classMyClass{publicstaticvoidHello(strings){Console.WriteLine(您好,{0}!,s);}publicstaticvoidGoodbye(strings){Console.WriteLine(再见,{0}!,s);}}《.NET技术》10ofpageclassProgram{staticvoidMain(string[]args){MyDelegatea,b,c,d;//创建引用Hello方法的委托对象aa=MyClass.Hello;Console.WriteLine(调用委托变量a:);a(a);//创建引用Goodbye方法的委托对象bb=MyClass.Goodbye;Console.WriteLine(调用委托变量b:);b(b);请仔细看以下代码:《.NET技术》11ofpage//a和b两个委托合成c,c=a+b;Console.WriteLine(调用委托变量c:);c(c=a+b);//c将按顺序调用两个方法//从组合委托c中移除a,只留下b,用d代表移除结果,d=c-a;Console.WriteLine(调用委托变量d:);d(d=c-a);//后者仅调用Goodbye方法:Console.ReadKey();}}《.NET技术》12ofpage•上述代码中委托变量c组合了两个委托变量a和b,因而,它拥有两个函数,当执行“c(“c=a+b”);”时,将导致MyClass类的两个静态函数都被执行。•像c这样的委托变量又称为“多路委托变量”。•可以用加法运算符来组合单个委托变量为多路委托变量。也可以使用减法运算符从一个多路委托变量中移除某个委托变量。上述示例2运行结果为:《.NET技术》13ofpage17.1.3委托揭秘•编译器和CLR怎样来实现委托?•使用ildasm查看示例1Main()方法的代码:staticvoidMain(string[]args){MathOptDelegateoppDel;MathOptobj=newMathOpt();oppDel=obj.Add;Console.WriteLine(oppDel(1,2));//输出3}注意:通过委托变量间接调用对象obj的实例方法Add(),实际上调用的是MathOptDelegate类的Invoke()方法。这个Invoke()方法从何而来?《.NET技术》14ofpage委托定义语句:publicdelegateintMathOptDelegate(intvalue1,intvalue2);当编译器遇到这段代码时,它会产生如下面所示的一个完整的类定义:publicclassMathOptDelegate:System.MulticastDelegate{publicMathOptDelegate(Objecttarget,Int32methodPtr);publicvoidvirtualInvoke(Int32value1,Int32value2);publicvirtualIAsyncResultBeginInvoke(Int32value1,Int32value2,AsyncCallbackcallback,Objectobject);publicvirtualvoidEndInvoke(IAsyncResultresult);}《.NET技术》15ofpage委托定义语句:publicdelegateintMathOptDelegate(intvalue1,intvalue2);当编译器遇到这段代码时,它会产生如下面所示的一个完整的类定义:publicclassMathOptDelegate:System.MulticastDelegate{publicMathOptDelegate(Objecttarget,Int32methodPtr);publicvoidvirtualInvoke(Int32value1,Int32value2);publicvirtualIAsyncResultBeginInvoke(Int32value1,Int32value2,AsyncCallbackcallback,Objectobject);publicvirtualvoidEndInvoke(IAsyncResultresult);}类的构造器,它接收两个参数target和methodPtr。target:引用要调用方法的对象;methodPtr:是一个方法指针,代表要调用的对象方法。《.NET技术》16ofpage委托定义语句:publicdelegateintMathOptDelegate(intvalue1,intvalue2);当编译器遇到这段代码时,它会产生如下面所示的一个完整的类定义:publicclassMathOptDelegate:System.MulticastDelegate{publicMathOptDelegate(Objecttarget,Int32methodPtr);publicvoidvirtualInvoke(Int32value1,Int32value2);publicvirtualIAsyncResultBeginInvoke(Int32value1,Int32value2,AsyncCallbackcallback,Objectobject);publicvirtualvoidEndInvoke(IAsyncResultresult);}方法和源代码中指定的原型一样。对外界对象实例方法的调用通过Invoke()方法实现。《.NET技术》17ofpage委托定义语句:publicdelegateintMathOptDelegate(intvalue1,intvalue2);当编译器遇到这段代码时,它会产生如下面所示的一个完整的类定义:publicclassMathOptDelegate:System.MulticastDelegate{publicMathOptDelegate(Objecttarget,Int32methodPtr);publicvoidvirtualInvoke(Int32value1,Int32value2);publicvirtualIAsyncResultBeginInvoke(Int32value1,Int32value2,AsyncCallbackcallback,Objectobject);publicvirtualvoidEndInvoke(IAsyncResultresult);}用于实现异步调用。《.NET技术》18ofpage•编译器定义的类中有4个方法:一个构造器、Invoke、BeginInvoke,以及EndInvoke。•MathOptDelegate类的方法全部都是虚方法,其对应的方法IL代码为空。以Invoke()方法为例,其生成的IL代码如下(通过ILDasm查看):.methodpublichidebysignewslotvirtualinstanceint32Invoke(int32value1,int32value2)runtimemanaged{}//endofmethodMathOptDelegate::InvokeC#编译器为委托类型生成的所有方法体都为空!这个标记告诉CLR,此方法的IL指令将在运行时动态生成。《.NET技术》19ofpage17.1.4委托调用链•自定义委托其实是从MulticastDelegate类中派生出来的。•Delegate类代表委托基类,而MulticastDelegate类代表“多路广播委托”,言下之意是从Delegate类派生出的委托只能“装有”一个函数,而从MulticastDelegate类派生出来的委托则可以“装有”多个函数,这多个函数首尾相接为一个“委托调用链表”,包容于多路委托变量中(见下图)。•事实上C#编译器将我们定义的委托类型都处理为从MulticastDelegate派生。多路委托变量函数函数函数《.NET技术》20ofpage示例3介绍“委托调用链”的含义•首先定义一个委托类型MyDelegate与一个类ApublicdelegateintMyDelegate(intvalue);publicclassA{publicintf1(inti){