理解C#中的委托需要把握两个要点:
1)委托是方法指针。
2)委托是一个类,当对其进行实例化的时候,要将引用方法作为它的构造方法的参数。
基于第一点,设想这样一个场景:在点对点的文件传输过程中,我们要设计一个文件传输类,该传输类起码要满足下面的几项功能:
❑传输文件;
❑按照百分制通知传输进度;
❑传输类能同时被控制台应用程序和Winform应用程序使用。
我们知道,由于要让通知本身能被控制台应用程序和Winform应用程序使用,因此设计的这个文件传输类在进行进度通知时,就不能显式地调用:
Console.WriteLine("当前进度:"+fileProgress);
或者:
this.progressText.Text="当前进度:"+fileProgress;
理想的情况是,在需要通知的地方,全部将其置换成一个方法的指针,由调用者来决定该方法完成什么功能。这个方法指针在C#中即被理解成委托。可以像下面这样来声明文件进度委托:
public delegate void FileUploadedHandler(int progress);
而这个文件传输类最终可能是这样的:
class FileUploader
{
public delegate void FileUploadedHandler(int progress);
public FileUploadedHandler FileUploaded;
public void Upload()
{
int fileProgress=100;
while(fileProgress>0)
{
//传输代码,省略
fileProgress--;
if(FileUploader!=null)
{
FileUploader(fileProgress);
}
}
}
}
调用者在调用这个文件传输类的时候,应该同时为FileUploaded赋值。赋值过程也就是将自身所具有的和委托声明相同的声明方法名赋值给FileUploaded。这样,类型FileUploader在执行到下面这条语句的时候,就是在执行调用者自身的方法:
FileUploaded(fileProgress);
理解了“委托是方法指针”这一点后,再来理解“委托是一个类”。实际上,应该把以下语句:
public delegate void FileUploadedHandler(int progress);
看成是像下面这样的一个类:
class FileUploadedHandler:System.MulticastDelegate
{
public FileUploadedHandler(object@object,IntPtr method)
{
}
public virtual IAsyncResult BeginInvoke(int progress,
AsyncCallback callback,object@object)
{
}
public virtual void EndInvoke(IAsyncResult result)
{
}
public virtual void Invoke(int progress)
{
}
}
委托是一个类,这个工作由编译器为我们完成,使用ILDasm.exe查看,我们可以看到这个类,如图3-2所示。
图3-2 IL结构
调用委托方法:
FileUploaded(fileProgress);
其实调用的是:
FileUploaded.Invoke(fileProgress);
可以通过IL代码来验证这一点。下面给出Upload方法的IL代码:
.method public hidebysig instance void Upload()cil managed
{
//代码大小34(0x22)
.maxstack 2
.locals init([0]int32 fileProgress)
IL_0000:ldc.i4.s 100
IL_0002:stloc.0
IL_0003:br.s IL_001d
IL_0005:ldloc.0
IL_0006:ldc.i4.1
IL_0007:sub
IL_0008:stloc.0
IL_0009:ldarg.0
IL_000a:ldfld class ConsoleApplication2.Program/FileUploader/
FileUploadedHandler ConsoleApplication2.Program/
FileUploader::FileUploaded
IL_000f:brfalse.s IL_001d
IL_0011:ldarg.0
IL_0012:ldfld class ConsoleApplication2.Program/FileUploader/
FileUploadedHandler ConsoleApplication2.Program/
FileUploader::FileUploaded
IL_0017:ldloc.0
IL_0018:callvirt instance void ConsoleApplication2.Program/
FileUploader/FileUploadedHandler::Invoke(int32)
IL_001d:ldloc.0
IL_001e:ldc.i4.0
IL_001f:bgt.s IL_0005
IL_0021:ret
}//end of method FileUploader::Upload
可以看到,在IL_0018处就是对Invoke方法的调用。