.Net delegate类型:
委托跟回调函数是很有渊源的。回调其实跟通知机制有关,考虑这样一个基本的事件序列:a对象调用了b对象的某个方法,希望b对象在其方法完成之时调用a对象的某个方法。要实现这样的过程,要求b对象持有a对象的引用(引用一般作为b
对象方法的参数传入),且“知道”a对象的那个特定方法。这个方法就是我们说的那个回调函数。
本质上,.net的委托类型是一种类型安全的指向方法的对象(c++中如函数指针)亦或是指向一系列方法的对象。所谓类型安全指:类型必须匹配,变量或引用不能指向一个不是该类的对象,类型安全由编译器保障,违反类型安全编译时会给出提示。对函数指针而言,就是返回值,入口参数的数目,类型和顺序应一致。委托一般都是滞后调用,编写委托实现的人一般不知道具体何时该方法被调用。不象传统的C++方法指针,.net的委托是类且具有内建支持,可进行异步和广播式的调用。
委托跟事件event可自然的契合,一般是事件激发导致委托被调用。
委托对象含有三个重要的信息:
1〉被调用方法的地址;
2〉此方法的参数(如果有的话);
3>
方法的返回值(如果有的话).不同于C++函数指针,.net委托既可指向实例方法也可指向静态方法.
一旦委托被创建且提供了足够的信息,便可在运行时调用它指向的方法.每个委托都可被异步和同步的调用,此点简化了编程工作:使得我们不用手工创建和管理线程对象,就可以在第二个线程中执行我们的方法.
C#中创建委托使用delegate关键字,委托名可以是任何你认为有意义的词.但必须使你的委托匹配你想指向的方法的签名.当C#的编译器处理委托类型时,会自动地生成一个密封(sealed)类,该类继承自System.MulticastDelegate.(这个类的父类是System.Delegate),此类提供了必要的基础结构便于委托持有一个方法的列表,这些方法会在后期的某个时间被调用
通过查看中间语言代码il(用ildasm.exe),可发现编译器会为委托类产生三个公共方法.Invoke();BeginInvoke()和EndIvoke();Invoke()是主要的方法,用于调用委托维护的每个方法.向所有同步调用一样,方法会阻塞调用者直到它执行完毕.Invoke方法也不必显式的调用,它工作于后台.后两个方法需配合使用用于异步执行委托指向的方法.
他们会在独立的线程中执行.
委托的构造方法:
public TypeOfReturn MyDelegate(object target,uint
fuctionAddress),可以看出它需要目标对象,和目标方法的函数地址.这个很易理解,所有方法的调用都遵循object.functionName(),的格式.不过还缺少:参数和返回
值.他们在上面提到的三个方法中给出: public TypeOfReturnValue Invoke([TypeOfParameter
ParaX]*);
//其中TypeOfReturnValue是返回值的类型,中括号里的是可不出现或可出现多个的参数列.异步方法对:public
IAsyncResult BeginInvoke([TypeOfParameter ParaX]*);public
TypeOfReturnValue EndInvoke(IAsyncResult
result);可看出不同处:BeginInvoke先返回一个中间类型,在EndInvoke中才是目标返回值的类型;
Invoke 方法给人这样的感觉: A--->C ; 而BeginIvoke和EndInvoke: A---->B
B---->C 等价于A----C .
引入中间过程的原因是什么呢? 这个
问题以后在讨论吧:它跟异步执行有关,见后面的文章。下面是委托由编译器产生的伪码表示:哈尔滨网站设计
public sealed class DelegateName :
System.MulticastDelegate
{
public DelegateName (object target, uint functionAddress);
public delegateReturnValue
Invoke(allDelegateInputRefAndOutParams);
public IAsyncResult
BeginInvoke(allDelegateInputRefAndOutParams,
AsyncCallback cb, object state);
public delegateReturnValue
EndInvoke(allDelegateRefAndOutParams,
IAsyncResult result);
}
综述:C#委托,编译器会为其对应地产生一个密封(sealed)类,该类有三个核心方法,这些方法的参数和返回值基于委托的定义。
关键字delegate使得你自定义的委托是一个(“is-a”)MulticastDelegate.所有用该关键字修饰的定义类型都是MulticastDelegate的子类,而该类又是Delegate的子类:(public
abstract class Delegate : ICloneable, ISerializable)
以下是我的简单学习测试:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace DelegateTest
{
public
delegate string MyDelegateType(string firstArg,string
secondArg);//创建了一个委托
public
class SomeType {
public
static string ClassMethodX(string str1,string str2){
System.Console.WriteLine("enter the static method <--");
try
{
System.Console.WriteLine("一些对参数的处理工作.......");
return String.Format("parameter1:{0} and Parameter2{1} come from a
static Method", str1, str2);
}
finally {
System.Console.WriteLine("end the static method-->");
}
}
public
string InstanceMethodX(string str1,string str2) {
System.Console.WriteLine("enter the instance method <--");
try
{
System.Console.WriteLine("一些对参数的处理工作.......");
return String.Format("parameter1:{0} and Parameter2{1} come from a
instance Method", str1, str2);
}
finally
{
System.Console.WriteLine("end the instance method-->");
}
}
}
//程序入口:
class
Program
{
static void Main(string[] args)
{
MyDelegateType myDelegate = new
MyDelegateType(SomeType.ClassMethodX);//创建一个委托,帮定到一个静态方法上
System.Console.WriteLine("开始调用委托");
System.Console.WriteLine(myDelegate("参数一","参数二"));
System.Console.WriteLine("结束调用委托");
SomeType aUDTType = new
SomeType();//实例化一个自定义的类实例(UDT--表示用户定义类型)
myDelegate += new MyDelegateType(aUDTType.InstanceMethodX);
System.Console.WriteLine("开始调用委托");
System.Console.WriteLine(myDelegate("参数一", "参数二"));
System.Console.WriteLine("结束调用委托");
System.Console.ReadLine();
}
}
}
再次重申:委托是可指向方法或方法列的对象,是安全的方法指针。
任何与委托格式匹配的方法(静态的或实例的),都可由委托指向(作为构造委托的参数传递到构造方法中去),只要参数信息(类型,数目,顺序)和返回值类型匹配,就可用于构造委托,与方法的名字没有任何关系。委托的内部维护了一个集合,估计是一ArrayList(仅是猜测啊!不过一般不必太在意到底是具体何种集合)。使用+=/combine()方法可将新的委托添加到原有委托上,-=/Remove()会从队列中摘掉一个委托成员,至于,委托的加入顺序与其执行顺序,留给朋友们了^-^;想想Oracle的
触发器的添加和执行顺序:(后添加的先执行).哈尔滨网站设计
委托的调用:用委托的实例名,加括号并传入参数即可。相当委托的实例名就是一个方法名,按方法的调用方式来就行。(底层其实
调用的是实例的Invoke方法,此点对程序员透明,用ildasm.exe可查看)当然也显式调用Invoke():myDelegateInstance
(argList);和myDelegateInstance.Invoke(argList);等效果,不过多用前者形式。
委托内部探查:
foreach (Delegate d in delegateObj.GetInvocationList())
{
Console.WriteLine("Method Name: {0}", d.Method);
Console.WriteLine("Type Name: {0}", d.Target);
}
委托是一种类型,他可如常规类定义的出现位置那样出现,可以在类内部声明----这也是最常用的最自然的方式,编译器为我们产生的
对应类型就是嵌套类了,当然也有通用的委托如:处理GUI按钮单击,拖拽,键盘敲击等的统一的委托类,你会发现他们的事件处理
方法的参数和返回类型都很一致。
委托作为成员:在你定义了一个委托后,可声明委托的变量来作为类的成员(方法变量成员)。如同常规的类成员一样,可以用访问修饰符去修饰它,也可以声明多个同类型委托的实例,每个实例在不同的情况下使用(委托的调用参考上面所述)。当委托是私有成员的时候,可能需要你提供公有的注册和注销的接口,但是是类型安全的常规解决方案(成员私有访问,成员的方法签名为公有)。哈尔滨软件开发
一点注意:在调用委托时最好有非空的判断逻辑,不然可能引发NullReferenceException异常。
委托的多播调用:委托天然具有多播功能,其+=操作可往委托持有的列表中添加一个新的委托,+=是Combine()方法的简明方式,
也可直接调用其Combine(),不过用+=似乎更符合C#
的习惯法.对应的-=操作(Remove()的简明用法)同此理.需注意的是,-=操作的参数
需有匹配的签名方法.并非实际的那个委托实例被删除.其实不必持有已合并的委托的实例(当然第一个委托的实例还是需要的),一般
用myExistDelegate += new
MyDelegate(someObj.method()).删除时可用myExistDelegate -= new
MyDelegate(someObj.method());
只要方法签名匹配即可.
委托可被用作方法的参数传递,就像其他的.net类型一样.
诡异的用法:
像下面的委托的定义:public delegate SomeType
SomeDelegate(.....);此委托是一个可返回某类型SomeType的对象,任何可返回
SomeType且参数信息匹配的方法都可用于实例化一个SomeDelegate.设想一个方法返回的是SomeType的子类型对象,且参数信息匹配
SomeDelegate的参数信息.是否可用于实例化一个SomeDelegate呢?.net2.0之前,是不可以的,必须再创建一个委托类型,其返回类型是
SomeType
的子类型.参考经典的继承法则,创建一个委托类型,它可指向任何返回SomeType或其子类的方法(方法的签名匹配委托定义)
这是很自然的.术语称为relaxed
delegates----松弛委托.考虑经典的工厂方法(Gof23中设计模式的一种),它的特点是否跟此特征很
像呢!!!;
创建泛型委托:
泛型编程或模板编程,是更抽象的编程,模板类可称为是类的蓝图.用模版你甚至可以创建出类.(参考说法: 类是对象的蓝图,类是对象
的构造工厂)...比如常规的集合类型,多有模板版本和常规的版本, System
.Collections.Generic.LinkedList,模版版本的集合
需要传入类型去替换T.如System.Collections.Generic.LinkedList strList = new
LinkedList();便是创建了一
个只能存储字符型的连接链表.委托也可有模版式的,如: public delegate void
MyGenericDelegate(T arg);
以上就是一个泛型委托,它是一个可返回空的委托模版,可用它来创建一个委托如: MyGenericDelegate intTarget
=
new
MyGenericDelegate(IntTarget);其中intTarget就是参数是一个int型且返回值是void的方法.这里假设intTarget是静态方
法.可用非泛化模拟泛型.只要让所有出现类型的地方用object类型就可达到泛型的效果.如:public delegate
void MyDelegate
(object
arg);和上面的模版版本效果一样,不过非类型安全而已.因为泛类型System.Object可指向任何类型的实例.而非上面的参数哈尔滨网站制作只可是int型.模版实例化过程:把T用具体类型换掉.之后的使用过程会得到编译器的类型检查帮助.
以Object类型作为参数的编程常
需要具体的型别检查逻辑,而使用模板可避免此问题.