深入理解C#中的Delegate
在c#中,event与delegate是两个非常重要的概念。因为在Windows应用程序中,对事件的使用非常频繁,而事件的实现依赖于delegate。
下面是对网上一些比较好的关于delegage的资料的整理,以及自己的一些想法。
Delegate是什么?
Delegate中文翻译为“委托”。Msdn中对Delegate的解释如下:
C#中的委托类似于C或C++中的函数指针。使用委托使程序员可以将方法引用封装在委托对象内。然后可以将该委托对象传递给可调用所引用方法的代码,而不必在编译时知道将调用哪个方法。与C或C++中的函数指针不同,委托是面向对象、类型安全的,并且是安全的。
一旦为委托分配了方法,委托将与该方法具有完全相同的行为。委托方法的使用可以像其他任何方法一样,具有参数和返回值,如下面的示例所示:
publicdelegatevoidDel(stringmessage);
与委托的签名(由返回类型和参数组成)匹配的任何方法都可以分配给该委托。这样就可以通过编程方式来更改方法调用,还可以向现有类中插入新代码。只要知道委托的签名,便可以分配自己的委托方法。
调用委托
构造委托对象时,通常提供委托将包装的方法的名称或使用匿名方法。实例化委托后,委托将把对它进行的方法调用传递给方法。调用方传递给委托的参数被传递给方法,来自方法的返回值(如果有)由委托返回给调用方。这被称为调用委托。可以将一个实例化的委托视为被包装的方法本身来调用该委托。例如:
//Createamethodforadelegate. publicstaticvoidDelegateMethod(stringmessage) { System.Console.WriteLine(message); } //Instantiatethedelegate. Delhandler=DelegateMethod; //Callthedelegate. handler("HelloWorld");
将委托构造为包装实例方法时,该委托将同时引用实例和方法。除了它所包装的方法外,委托不了解实例类型,所以只要任意类型的对象中具有与委托签名相匹配的方法,委托就可以引用该对象。将委托构造为包装静态方法时,它只引用方法。
回调
由于实例化委托是一个对象,所以可以将其作为参数进行传递,也可以将其赋值给属性。这样,方法便可以将一个委托作为参数来接受,并且以后可以调用该委托。这称为异步回调,是在较长的进程完成后用来通知调用方的常用方法。以这种方式使用委托时,使用委托的代码无需了解有关所用方法的实现方面的任何信息。此功能类似于接口所提供的封装。
回调的另一个常见用法是定义自定义的比较方法并将该委托传递给排序方法。它允许调用方的代码成为排序算法的一部分。下面的示例方法使用Del类型作为参数:
publicvoidMethodWithCallback(intparam1,intparam2,Delcallback) { callback("Thenumberis:"+(param1+param2).ToString()); }
然后可以将上面创建的委托传递给该方法:
MethodWithCallback(1,2,handler);
在控制台中将收到下面的输出:
Thenumberis:3
使用委托的好处
委托允许类设计器分离类型声明和实现。
在将委托用作抽象概念时,MethodWithCallback不需要直接调用控制台--设计它时无需考虑控制台。MethodWithCallback的作用只是准备字符串并将该字符串传递给其他方法。此功能特别强大,因为委托的方法可以使用任意数量的参数。
将方法作为参数进行引用的能力使委托成为定义回调方法的理想选择。例如,可以向排序算法传递对比较两个对象的方法的引用。分离比较代码使得可以采用更通用的方式编写算法。
如何使用委托
1.声明委托
声明一个新的委托类型。每个委托类型都描述参数的数目和类型,以及它可以封装的方法的返回值类型。每当需要一组新的参数类型或新的返回值类型时,都必须声明一个新的委托类型。
2.实例化委托
声明了委托类型后,必须创建委托对象并使之与特定方法关联。方法的签名应与委托定义的签名一致。
委托对象可以关联静态方法,也可以关联非静态方法。
委托一旦创建,它的关联方法就不能更改;委托对象是不可变的。
3.调用委托
创建委托对象后,通常将委托对象传递给将调用该委托的其他代码。通过委托对象的名称(后面跟着要传递给委托的参数,括在括号内)调用委托对象。
下面是一个示例:
usingSystem; publicclassSamplesDelegate { //DeclaresadelegateforamethodthattakesinanintandreturnsaString. publicdelegateStringmyMethodDelegate(intmyInt); //Definessomemethodstowhichthedelegatecanpoint. publicclassmySampleClass { //Definesaninstancemethod. publicStringmyStringMethod(intmyInt) { if(myInt>0) return("positive"); if(myInt<0) return("negative"); return("zero"); } //Definesastaticmethod. publicstaticStringmySignMethod(intmyInt) { if(myInt>0) return("+"); if(myInt<0) return("-"); return(""); } } publicstaticvoidMain() { //Createsonedelegateforeachmethod. mySampleClassmySC=newmySampleClass(); myMethodDelegatemyD1=newmyMethodDelegate(mySC.myStringMethod); myMethodDelegatemyD2=newmyMethodDelegate(mySampleClass.mySignMethod); //Invokesthedelegates. Console.WriteLine("{0}is{1};usethesign/"{2}/".",5,myD1(5),myD2(5)); Console.WriteLine("{0}is{1};usethesign/"{2}/".",-3,myD1(-3),myD2(-3)); Console.WriteLine("{0}is{1};usethesign/"{2}/".",0,myD1(0),myD2(0)); } } /* Thiscodeproducesthefollowingoutput: 5ispositive;usethesign"+". -3isnegative;usethesign"-". 0iszero;usethesign"". */
多路广播
调用委托时,它可以调用多个方法。这称为多路广播。若要向委托的方法列表(调用列表)中添加额外的方法,只需使用加法运算符或加法赋值运算符(“+”或“+=”)添加两个委托。例如:
MethodClassobj=newMethodClass(); Deld1=obj.Method1; Deld2=obj.Method2; Deld3=DelegateMethod; //Bothtypesofassignmentarevalid. DelallMethodsDelegate=d1+d2; allMethodsDelegate+=d3;
此时,allMethodsDelegate在其调用列表中包含三个方法--Method1、Method2和DelegateMethod。原来的三个委托d1、d2和d3保持不变。调用allMethodsDelegate时,将按顺序调用所有这三个方法。如果委托使用引用参数,则引用将依次传递给三个方法中的每个方法,由一个方法引起的更改对下一个方法是可见的。如果任一方法引发了异常,而在该方法内未捕获该异常,则该异常将传递给委托的调用方,并且不再对调用列表中后面的方法进行调用。如果委托具有返回值和/或输出参数,它将返回最后调用的方法的返回值和参数。若要从调用列表中移除方法,请使用减法运算符或减法赋值运算符(“-”或“-=”)。例如:
//removeMethod1 allMethodsDelegate-=d1; //copyAllMethodsDelegatewhileremovingd2 DeloneMethodDelegate=allMethodsDelegate-d2;
多路广播委托广泛用于事件处理中。事件源对象向已注册接收该事件的接收方对象发送事件通知。为了为事件注册,接收方创建了旨在处理事件的方法,然后为该方法创建委托并将该委托传递给事件源。事件发生时,源将调用委托。然后,委托调用接收方的事件处理方法并传送事件数据。给定事件的委托类型由事件源定义。
本示例演示如何组合多路广播委托。委托对象的一个用途在于,可以使用+运算符将它们分配给一个要成为多路广播委托的委托实例。组合的委托可调用组成它的那两个委托。只有相同类型的委托才可以组合。
运算符可用来从组合的委托移除组件委托。
delegatevoidDel(strings); classTestClass { staticvoidHello(strings) { System.Console.WriteLine("Hello,{0}!",s); } staticvoidGoodbye(strings) { System.Console.WriteLine("Goodbye,{0}!",s); } staticvoidMain() { Dela,b,c,d; //Createthedelegateobjectathatreferences //themethodHello: a=Hello; //Createthedelegateobjectbthatreferences //themethodGoodbye: b=Goodbye; //Thetwodelegates,aandb,arecomposedtoformc: c=a+b; //Removeafromthecomposeddelegate,leavingd, //whichcallsonlythemethodGoodbye: d=c-a; System.Console.WriteLine("Invokingdelegatea:"); a("A"); System.Console.WriteLine("Invokingdelegateb:"); b("B"); System.Console.WriteLine("Invokingdelegatec:"); c("C"); System.Console.WriteLine("Invokingdelegated:"); d("D"); } }
Delegate的总结
delegate是C#中的一种类型,它实际上是一个能够持有对某个方法的引用的类。与其它的类不同,delegate类能够拥有一个签名(signature),并且它只能持有与它的签名相匹配的方法的引用。它所实现的功能与C/C++中的函数指针十分相似。它允许传递一个类A的方法m给另一个类B的对象,使得类B的对象能够调用这个方法m。但与函数指针相比,delegate有许多函数指针不具备的优点。首先,函数指针只能指向静态函数,而delegate既可以引用静态函数,又可以引用非静态成员函数。在引用非静态成员函数时,delegate不但保存了对此函数入口指针的引用,而且还保存了调用此函数的类实例的引用。其次,与函数指针相比,delegate是面向对象、类型安全、可靠的受控(managed)对象。也就是说,runtime能够保证delegate指向一个有效的方法,你无须担心delegate会指向无效地址或者越界地址。
自己对