从头到尾看委托

    目录

  1. 委托介绍
  2. 何处定义委托?
  3. 委托既可以封装静态方法,也可以封装实例方法,还可以封装匿名方法
  4. 处理发布、订阅关系的几种方式
  5. 委托本质
  6. 为什么即有Delegate类,又有MulticastDelegate类,这两个类有什么区别?
  7. 委托判等
  8. 获取委托链中各个委托的返回值

    安全性
    委托相对于其它语言的回调函数,最大的好处在与其的安全性。
    委托是回调函数的安全类型包装(相对于非托管程序)。C++编写的非托管程序进行回调时很容易出错(C中的函数指针只不过是一个指向存储单元的指针,我们无法说出这个指针实际指向什么,像参数和返回类型等就更无从知晓了)。由于委托的存在,托管应用程序不会出现这样的情况。委托通常用来定义响应事件的回调方法的签名。
    C#中的委托类似于C或C++中的函数指针。使用委托使程序员可以将方法引用封装在委托对象内( 所以这里的“引用”不是原始内存地址,而是包装了方法的内存地址的委托实例 )。然后可以将给委托对象传递可调用所引用方法的代码,而不必在编译时知道将调用哪个方法。与C或C++中的函数指针不同,委托是面向对象、类型安全的,并且是安全的。
    委托声明定义一种类型,它用一组特定的参数以及返回类型封装方法。
    对于静态方法,委托对象封装要调用的方法。
    对于实例方法,委托对象同时封装一个实例和该实例上的一个方法。
    如果你有一个委托对象和一组适当的参数,则可以用这些参数调用该委托。
    委托的一个有趣且有用的属性是: 它不知道或不关心自己引用的对象的类,任何对象都可以,只是方法的参数类型必须与委托的参数类型和返回类型相匹配。   
    另外一点,委托实现了发布委托的类与订阅委托的类之间实现了解耦。发布委托的类只管向外面其它类公布,我这里有这样一个“接口”,具体运行时调用时会执行成什么样子,发布委托类不管;订阅委托的类只管实现如何做就可以了,具体怎么调用到它的,订阅委托类不用管。  

    定义委托实际上是定义一个继承自System.MultiCastDelegate的一个类,所以定义类的地方就可以定义委托。
    个人认为,如果定义了一个委托,不止一个类要发布该委托,那么该委托定义就应该放在类的外面进行定义;如果定义的委托就是给某个发布者使用的,那么直接定义在这个发布者类里面就可以了。

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. namespace DelegatePractise
  6. {
  7.     #region 定义委托
  8.     public delegate void ShowInfoHandler(string info);
  9.     #endregion
  10.     #region 委托发布者
  11.     class Distributor 
  12.     {
  13.         public ShowInfoHandler handler;
  14.         public void show( string something) 
  15.         {
  16.             if ( this.handler != null )
  17.              {
  18.                  handler(something);
  19.              }
  20.         }
  21.     }
  22.     #endregion
  23.     #region 委托订阅者
  24.     class Subscriber 
  25.     {
  26.         //实现订阅的实例方法
  27.         public void InstanceMethod(string something) 
  28.         {
  29.             Console.WriteLine("i am in instance method!" + something);
  30.         }
  31.         //实现订阅的静态方法
  32.         public static void SaySomething(string something) 
  33.         {
  34.             Console.WriteLine( "i am in static method!" + something );
  35.         }
  36.     }
  37.     #endregion
  38.     class Test
  39.     {
  40.         static void Main(string[] args)
  41.         {
  42.             Distributor distributor = new Distributor();
  43.             Subscriber subscriber = new Subscriber();
  44.             //委托封装实例方法
  45.             distributor.handler += subscriber.InstanceMethod;
  46.             //委托封装静态方法
  47.             distributor.handler += Subscriber.SaySomething;
  48.             //委托封装匿名方法
  49.             distributor.handler += delegate(string noName)
  50.             {
  51.                 Console.WriteLine("i am in a noname method!" + noName);
  52.             };
  53.             distributor.show("haha");
  54.             Console.ReadLine();
  55.         }
  56.     }
  57. }

 

    第一种方式:由第三方类来关联发布者和订阅者
    在这种方式下,发布者只管发布,订阅者只管实现,关联关系在第三方类中进行。代码示例如下:

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. namespace DelegatePractise
  6. {
  7.     #region 定义委托
  8.     public delegate void ShowInfoHandler(string info);
  9.     #endregion
  10.     #region 委托发布者
  11.     class Distributor 
  12.     {
  13.         //要发布出去的东东
  14.         public ShowInfoHandler showInfoDel;
  15.         public void show(string something) 
  16.         {
  17.              if ( showInfoDel != null )
  18.              {
  19.                  showInfoDel( something );
  20.              }
  21.         }
  22.     }
  23.     #endregion
  24.     #region 委托订阅者
  25.     class Subscriber 
  26.     {
  27.         //实现订阅的方法
  28.         public void SaySomething(string something) 
  29.         {
  30.             Console.WriteLine( something );
  31.         }
  32.     }
  33.     #endregion
  34.     class Test
  35.     {
  36.         static void Main(string[] args)
  37.         {
  38.             Distributor distributor = new Distributor();
  39.             Subscriber subscriber = new Subscriber();
  40.             //关联发布者和订阅者之间的订阅关系
  41.             distributor.showInfoDel += subscriber.SaySomething;
  42.             distributor.show("我是要显示的东东!");
  43.             Console.ReadLine();
  44.         }
  45.     }
  46. }

    从上面的例子中可看出,发布者和订阅者的关联关系是在第三方类Test中关联起来的。
    如果订阅者比较少,可以使用这种方式,但是,如果订阅者很多、并且没什么规律的话,这样来关联发布者和订阅者就很累了,让订阅者自己内部来进行订阅,就轻松一些。 
    第二种方式:订阅者使用方法来订阅发布者发布的委托 
    在这种方式下,订阅者使用方法订阅发布者发布的委托,不用第三方类来进行关联发布者和订阅者。代码如下:

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. namespace DelegatePractise
  6. {
  7.     #region 定义委托
  8.     public delegate void ShowInfoHandler(string info);
  9.     #endregion
  10.     #region 委托发布者
  11.     class Distributor 
  12.     {
  13.         //要发布出去的东东
  14.         public ShowInfoHandler showInfoDel;
  15.         public void show(string something) 
  16.         {
  17.              if ( showInfoDel != null )
  18.              {
  19.                  showInfoDel( something );
  20.              }
  21.         }
  22.     }
  23.     #endregion
  24.     #region 委托订阅者
  25.     class Subscriber 
  26.     {
  27.         //实现订阅的方法
  28.         public void SaySomething(string something) 
  29.         {
  30.             Console.WriteLine( something );
  31.         }
  32.         //订阅者使用方法来订阅发布者的委托
  33.         public void SubscribeDel(Distributor dis) 
  34.         {
  35.             dis.showInfoDel += SaySomething;
  36.         }
  37.     }
  38.     #endregion
  39.     class Test
  40.     {
  41.         static void Main(string[] args)
  42.         {
  43.             Distributor distributor = new Distributor();
  44.             Subscriber subscriber = new Subscriber();
  45.             //订阅者订阅发布者的委托
  46.             subscriber.SubscribeDel(distributor);
  47.             distributor.show("我是要显示的东东!");
  48.             Console.ReadLine();
  49.         }
  50.     }
  51. }

    第三种方式:使用static readonly字段

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. namespace DelegatePractise
  6. {
  7.     #region 定义委托
  8.     public delegate void ShowInfoHandler(string info);
  9.     #endregion
  10.     #region 委托发布者
  11.     class Distributor 
  12.     {
  13.         public void show( ShowInfoHandler handler,string something) 
  14.         {
  15.              if ( handler != null)
  16.              {
  17.                  handler(something);
  18.              }
  19.         }
  20.     }
  21.     #endregion
  22.     #region 委托订阅者
  23.     class Subscriber 
  24.     {
  25.         //在类里面已经使用static readonly的字段的方式把委托和方法关联好了
  26.         //注意使用这种方式时,只能使用静态的方法和静态的字段
  27.         public static readonly ShowInfoHandler handler = new ShowInfoHandler( SaySomething );
  28.         //实现订阅的方法
  29.         public static void SaySomething(string something) 
  30.         {
  31.             Console.WriteLine( something );
  32.         }
  33.     }
  34.     #endregion
  35.     class Test
  36.     {
  37.         static void Main(string[] args)
  38.         {
  39.             Distributor distributor = new Distributor();
  40.             distributor.show(Subscriber.handler, "hello");
  41.             Console.ReadLine();
  42.         }
  43.     }
  44. }

    实际上,在上面的例子中,发布者并没有定义一个委托字段来发布一个委托,但它使用了一个带委托类型参数的方法,故我们还把它当做发布者。这种方式下,发布者只管使用委托,具体委托关联了哪些方法,它不管;委托具体如何关联,以及被调用后做什么,全部都在订阅者中进行实现,很好的实现了类与类之间的解耦。
    第四种方式:订阅者使用属性代替static readonly委托字段
    使用static readonly字段方式的问题在于,不管你有没有使用到这个委托,你都必须实例化。故可以优化为使用属性方式,使用到该属性时,才返回一个委托实例。具体代码如下:

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. namespace DelegatePractise
  6. {
  7.     #region 定义委托
  8.     public delegate void ShowInfoHandler(string info);
  9.     #endregion
  10.     #region 委托发布者
  11.     class Distributor 
  12.     {
  13.         public void show( ShowInfoHandler handler,string something) 
  14.         {
  15.              if ( handler != null)
  16.              {
  17.                  handler(something);
  18.              }
  19.         }
  20.     }
  21.     #endregion
  22.     #region 委托订阅者
  23.     class Subscriber 
  24.     {
  25.         public static ShowInfoHandler Handler 
  26.         {
  27.             get 
  28.             {
  29.                 return new ShowInfoHandler( SaySomething );
  30.             }
  31.         }
  32.         //实现订阅的方法
  33.         public static void SaySomething(string something) 
  34.         {
  35.             Console.WriteLine( something );
  36.         }
  37.     }
  38.     #endregion
  39.     class Test
  40.     {
  41.         static void Main(string[] args)
  42.         {
  43.             Distributor distributor = new Distributor();
  44.             distributor.show(Subscriber.Handler, "hello");
  45.             Console.ReadLine();
  46.         }
  47.     }
  48. }

    我们先来把第一种发布委托方式的代码反编译,来看看能发现什么,IL代码如下:

  1. .namespace DelegatePractise
  2. {
  3.     .class private auto ansi beforefieldinit Distributor
  4.         extends [mscorlib]System.Object
  5.     {
  6.         .method public hidebysig specialname rtspecialname instance void .ctor() cil managed
  7.         {
  8.         }
  9.         .method public hidebysig instance void show(string something) cil managed
  10.         {
  11.         }
  12.         .field public class DelegatePractise.ShowInfoHandler showInfoDel
  13.     }
  14.     .class public auto ansi sealed ShowInfoHandler
  15.         extends [mscorlib]System.MulticastDelegate
  16.     {
  17.         .method public hidebysig specialname rtspecialname instance void .ctor(object 'object', native int 'method') runtime managed
  18.         {
  19.         }
  20.         .method public hidebysig newslot virtual instance class [mscorlib]System.IAsyncResult BeginInvoke(string info, class [mscorlib]System.AsyncCallback callback, object 'object') runtime managed
  21.         {
  22.         }
  23.         .method public hidebysig newslot virtual instance void EndInvoke(class [mscorlib]System.IAsyncResult result) runtime managed
  24.         {
  25.         }
  26.         .method public hidebysig newslot virtual instance void Invoke(string info) runtime managed
  27.         {
  28.         }
  29.     }
  30.     .class private auto ansi beforefieldinit Subscriber
  31.         extends [mscorlib]System.Object
  32.     {
  33.         .method public hidebysig specialname rtspecialname instance void .ctor() cil managed
  34.         {
  35.         }
  36.         .method public hidebysig instance void SaySomething(string something) cil managed
  37.         {
  38.         }
  39.     }
  40.     .class private auto ansi beforefieldinit Test
  41.         extends [mscorlib]System.Object
  42.     {
  43.         .method public hidebysig specialname rtspecialname instance void .ctor() cil managed
  44.         {
  45.         }
  46.         .method private hidebysig static void Main(string[] args) cil managed
  47.         {
  48.             .entrypoint
  49.         }
  50.     }
  51. }

    从上面的IL代码中我们会发现比我们原先写的代码中多了一个类:ShowInfoHandler,该类正是我们定义的委托的名字,这说明定义一个委托,实际上就是定义一个类,只是在c#语言中允许我们像那样来写罢了,到了底层,还是一个类。我们能够从生成的类中发现,该类有四个方法,分别为构造函数、同步invoke方法、异步invoke方法、异步结束方法。
    在构造函数中有两个参数,第一个参数为委托的target,如果关联的方法为静态方法,则此object为null,第二个参数表示关联方法对应内存中的int入口地址;
    Invoke方法的参数跟我们定义委托时的方法签名密切相关,那个方法签名的参数是什么样子,这里Invoke方法的参数就是什么样子;
    BeginInvoke方法的参数也是跟定义委托时的方法签名密切相关,那个方法签名的参数是什么样子,BeginInvoke方法的前几个参数就是什么样子,它的倒数第二个参数也是个异步委托的实例,表示该异步方法结束时要调用的回调函数,最后一个参数一般就是把当前调用BeginInvoke方法的委托自己传进去。
    EndInvoke方法有一个参数,必须是IAsyncResult类型的,该参数含有异步调用的结果信息。另外注意,IAsyncResult类型的这个参数是必须的,委托方法签名中的in/out参数也要带进来,返回值类型则就是委托签名中的返回值类型。
    另外,注意看的话,会发现委托生成的这个类是继承自System.MulticastDelegate类的, 而且生成的委托类的每个方法都标识为runtime managed,而不是cil managed,表示这个类里面究竟要干什么,在编译时是不确定的,只有到了运行时才能够得知,所以方法体内都是空的。  
    下面,我们来看看MulticastDelegate类中几个重要的私有字段:
    1)_target,类型为System.Object,指向委托被调用时应该操作的对象。该字段用于实例方法的回调,如果委托关联的方法是静态方法,则此字段为null。如果跟反射关联起来,该字段以及_methodPtr这两个字段非常有用,Delegate类中有相应的属性来访问相应的字段。具体委托与事件的关联应用,后面会有描述。
    2)_methodPtr,类型为System.Int32,标识关联方法在内存中被调用时的入口地址。
    3)_prev,类型为System.MulticastDelegate,表示在委托链中上一个委托,如果本委托就是委托链的头,则此字段就是null。该字段在委托判等时非常必要。    

     微软刚开始设计c#语言的时候,Delegate类是用在单播模式下,MulticastDelegate类是用在多播模式下,在编译的时候,编译器如果发现该委托签名返回值是null,则编译器认为它是多播模式,自动生成类并继承自MulticastDelegate,如果该委托方法签名返回值类型不是null,则编译器认为它是单播模式,自动生成类并继承自Delegate。但是在后面的很长一段时间里,接近c#快要发布的时候,发现很多时候虽然实现委托的方法有返回值,但调用委托的时候,这些返回值并不重要,即想让有返回值的委托类型也继承自MulticastDelegate,这下可麻烦了,如果是在设计的早期发现这个问题,那么很容易就沟通解决掉了,但是,在快要发布的这个时候,如果重新去设计,就要牵涉CLR小组,C#语言小组,编译器小组等,微软想想,靠,为了这么点个小功能,让我伤筋动骨,本来不稳定的名声就在外了,这次如果改一下,如果测试又不彻底,nnd就给人留下把柄了,不划算!不划算归不划算,问题还是要处理,好在微软还可以修改自己的C#编译器,这样修改了编译器后,只要是委托,就都继承自MulticastDelegate了,MulticastDelegate自己呢就继承自Delegate,Delegate则继承自Object,这就是委托的继承体系,Delegete类目前呢,里面是还有好几个非常有用的静态方法的,后面的章节会介绍到。

      上面已经说过,Delegate类继承自Object类。但是Delegate类重写了Object类的Equals()方法,比较的是委托的_target字段和_method字段是否一致,如果两个都一致,那么就返回true,如果有一个不一致,就返回false。但实际上我们的所有委托实际上都是继承自MulticastDelegate的,而MulticastDelegate又重写了Delegate的Equals()方法,它先判断两个委托的_target和_method以及_prev是否一致,如果不一致,则返回false,如果一致,则接着判断,两个委托链是否一样长,不一样长,也返回false,如果一样长,则接着判断各自_prev指向的委托(实际上也是委托链)是否相等,这样判断,一直到委托链的头。

       直接上代码,关键是使用GetInvocationList()方法。

      

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值