编写高质量代码改善C#程序的157个建议——建议39:了解委托的实质

建议39:了解委托的实质

理解C#中的委托需要把握两个要点:

1)委托是方法指针。

2)委托是一个类,当对其进行实例化的时候,要将引用方法作为它的构造方法的参数。

设想这样一个场景:在点对点文件传输过程当中,我们要设计一个文件传输类,该传输类起码要满足下面几项功能:

  • 传输问题件;
  • 按照百分制通知传输进度;
  • 传输类能够同时被控制台程序和WinForm应用程序使用。

由于要让通知本身能够被控制台程序和WinFrom应用程序使用,因此设计这个文件传输类在进行进度通知时,就不能显示调用:

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 (FileUploaded != null)
                {
                    FileUploaded(fileProgress);
                }
            }
        }
    }

调用者在调用这个文件传输类的时候,应该同时为FileUploaded赋值,赋值过程中也就是将自身所具有的和委托声明相同的声明方法赋值给FileUploaded。这样,类型FileUploader在执行到下面的代码时,就是执行调用者自身的方法

 FileUploaded(fileProgress);

理解了“委托是方法指针”这一点后,在了理解“委托是一个类”。

查看下面这句话:

public delegate void FileUploadedHandler(int progress);

它的IL代码为:

.class auto ansi sealed nested public FileUploadedHandler
    extends [mscorlib]System.MulticastDelegate
{
    .method public hidebysig specialname rtspecialname instance void .ctor(object 'object', native int 'method') runtime managed
    {
    }

    .method public hidebysig newslot virtual instance class [mscorlib]System.IAsyncResult BeginInvoke(int32 progress, class [mscorlib]System.AsyncCallback callback, object 'object') runtime managed
    {
    }

    .method public hidebysig newslot virtual instance void EndInvoke(class [mscorlib]System.IAsyncResult result) runtime managed
    {
    }

    .method public hidebysig newslot virtual instance void Invoke(int32 progress) runtime managed
    {
    }

}

调用委托方法:

FileUploaded(fileProgress);

其实是调用:

FileUploaded.Invok(fileProgress);

可以查看Upload方法的IL代码:

.method public hidebysig instance void Upload() cil managed
{
    .maxstack 2
    .locals init (
        [0] int32 fileProgress,
        [1] bool CS$4$0000)
    L_0000: nop 
    L_0001: ldc.i4.s 100
    L_0003: stloc.0 
    L_0004: br.s L_0028
    L_0006: nop 
    L_0007: ldloc.0 
    L_0008: ldc.i4.1 
    L_0009: sub 
    L_000a: stloc.0 
    L_000b: ldarg.0 
    L_000c: ldfld class MyTest.FileUploader/FileUploadedHandler MyTest.FileUploader::FileUploaded
    L_0011: ldnull 
    L_0012: ceq 
    L_0014: stloc.1 
    L_0015: ldloc.1 
    L_0016: brtrue.s L_0027
    L_0018: nop 
    L_0019: ldarg.0 
    L_001a: ldfld class MyTest.FileUploader/FileUploadedHandler MyTest.FileUploader::FileUploaded
    L_001f: ldloc.0 
    L_0020: callvirt instance void MyTest.FileUploader/FileUploadedHandler::Invoke(int32)
    L_0025: nop 
    L_0026: nop 
    L_0027: nop 
    L_0028: ldloc.0 
    L_0029: ldc.i4.0 
    L_002a: cgt 
    L_002c: stloc.1 
    L_002d: ldloc.1 
    L_002e: brtrue.s L_0006
    L_0030: ret 
}

可以看到L_0020处调用Invoke方法。

一句话:委托是一种数据类型,它用来传递方法。

转自:《编写高质量代码改善C#程序的157个建议》陆敏技