zoukankan      html  css  js  c++  java
  • 委托是什么

    目前大部分文章关注是如何使用委托?为什么要使用委托?
    却很少关注委托是什么?委托是如何工作的?明白这两个问题能帮助我们更好的理解使用委托。
    本文的内容 就是针对这两个问题。

    先看一个最简单的例子

     1     class Program
     2     {
     3         delegate void TestDelegate(int val);
     4         static void Main(string[] args)
     5         {
     6             TestDelegate Dele = new TestDelegate(Fun1);
     7             Dele += new Program().Fun2;
     8 
     9             Dele(2);
    10 
    11             Console.ReadKey();
    12         }
    13 
    14         static void Fun1(int a)
    15         {
    16             Console.Write(a);
    17         }
    18         void Fun2(int b)
    19         {
    20             Console.Write(b);
    21         }
    22     }

    这是我们看到的代码,对于委托只有一句:

     delegate void TestDelegate(int val);

    或许不太好理解 委托到底是什么。

    那么我们看IL代码,图1:

     

    我们可以看到
      命名空间 MyProject 下包含 类型 MyProject.Program

    类型MyProject.Program 下分别包含:

    一个类型 TestDelegate,正是我们声明的委托。
    构造函数:.ctor
    静态方法:Fun1
    实例方法:Fun2
    程序入口:Main 

    由此,我们可以知道 我们声明的委托TestDelegate是被编译成类型的。
    然后在看其内部信息:
      继承自System.MulticastDelegate
      构造函数:.ctor
      异步方法:BeginInvoke,EndInvoke,
      常规调用方法:Invoke

        其中,System.MulticastDelegate 继承自 System.Delegate。

    理清上面的关系,并且把各继承类中主要成员提取出来,于是我们上面的代码实际是这个样子:

     1 class Program
     2     {
     3         /// <summary>
     4         /// 委托类型,实际上System.Delegate提供了很多成员,这里只列出主要的成员。
     5         /// </summary>
     6         public sealed class TestDelegate
     7         {
     8             private object _invocationList;//Obiect类型,System.MulticastDelegate成员,委托列表,无委托列表时为null,创建委托列表时值为 Delegate[]
     9             private object _target;//Object类型,System.Delegate成员,当以实例方法创建委托时,保存该实例方法的对象。当以静态方法创建委托时,指向当前委托对象.
    10             private System.IntPtr _methodPtr;//IntPtr类型,System.Delegate成员,当以实例方法创建委托时,保存该实例的方法引用,运行时是该方法的内存地址。
    11             private System.IntPtr _methodPtrAux;//IntPtr类型,System.Delegate成员,当以静态方法创建委托时,保存静态的方法引用,运行时是该方法的内存地址。
    12             public System.Reflection.MethodInfo Method;//只读属性,返回System.Reflection.MethodInfo对象,其中包含_methodPtr或_methodPtrAux指向的方法(即注册委托的方法)的相关信息。
    13             public object Target;//只读属性,实例方法创建委托 返回_target,静态方法创建委托 返回null,
    14             ///以下主要方法的实现以文字描述,也方便理解。本已写了部分伪代码,但有些操作是编译器实现的,伪代码也不好写。所以文字描述。
    15             
    16             /// <summary>
    17             /// 构造函数,创建委托实例            
    18             /// </summary>
    19             /// <param name="target"></param>
    20             /// <param name="method"></param>
    21             protected TestDelegate(object target, string method) 
    22             {
    23                 //委托类的构造函数接受两个参数,但实际上我们创建的时候只传递了一个方法引用,为什么?        
    24                 //实际上编译器 会分析我们传入的参数,将类型的对象引用传递给target,方法引用传递给method.
    25                 初始化_invocationList==null;
    26         
    27                 当为实例方法时:
    28                     传递target 给_target
    29                     传递method给_methodPtr
    30         
    31                 当为静态方法时:
    32                     传递当前委托对象给_target,但此时访问属性Target时,返回null
    33                     method传递给_methodPtrAux    
    34             }
    35             /// <summary>
    36             /// 添加委托
    37             /// </summary>
    38             /// <param name="a"></param>
    39             /// <param name="b"></param>
    40             /// <returns></returns>
    41             public static Delegate Combine(Delegate a, Delegate b) 
    42             {
    43                 如果 a 和b 都为null ,抛异常
    44                 如果a==null,返回b,b==null,返回a
    45                 否则,合并a和b的委托列表(_invocationList),传递给b,返回b ;合并后,a委托列表在前,b委托列表在后           
    46             }   
    47             /// <summary>
    48             /// 删除
    49             /// </summary>
    50             /// <param name="source"></param>
    51             /// <param name="value"></param>
    52             /// <returns></returns>
    53             public static Delegate Remove(Delegate source, Delegate value)
    54             {
    55                 获取source._invocationList
    56         
    57                 如果source._invocationList 中包含value._invocationList
    58                     从source._invocationList中移除 value._invocationList
    59                     返回source
    60         
    61                 如果value==null 或 source._invocationList 中不包含value._invocationList
    62                     返回source
    63         
    64                 如果source==null 或 source._invocationList ==value._invocationList 
    65                     返回null
    66             }
    67             /// <summary>
    68             /// 调用
    69             /// </summary>
    70             /// <param name="value"></param>
    71             public void Invoke(int value)
    72             {
    73                 如果_invocationList为null,执行 _methodPtr.Invoke(_target,value)
    74                 否则,遍历_invocationList(其值为Delegate[]),调用每一个委托
    75             }
    76         }
    77         static void Main(string[] args)
    78         {
    79             TestDelegate Dele = new TestDelegate(Fun1);//调用构造函数,Fun1为静态方法,此时 Dele._target指向Dele自身
    80             Dele += new Program().Fun2;//Fun2为实例方法,此时此时 Dele._target指向new Program()对象
    81             //对于这一步的+=操作的具体步骤是:(注:编译器自动对委托实例重载了+=,-=操作,-=同理)
    82             //1、 new Program(),并获取该对象Fun2方法的引用;静态方法时,直接获取方法引用。
    83             //2、 new TestDelegate(),传入第一步方法引用为构造函数参数。
    84             //3、 调用Combine方法。参数分别为Dele和第二步的委托对象。
    85             
    86             Dele(2);//调用Invoke方法
    87             Console.ReadKey();
    88         }
    89         static void Fun1(int a)
    90         {
    91             Console.Write(a);
    92         }
    93         void Fun2(int b)
    94         {
    95             Console.Write(b);
    96         }
    97     }

    委托的实质是一个类,其内部 维护了注册方法的 类型引用,方法引用及本身的委托列表等成员。
    并提供了构造,添加,删除,调用等方法。最大的特色是可以对按顺序 调用 委托列表的中注册方法。

    然后再来看事件
    在上部分代码基础上添加事件。

     1     class Program
     2     {
     3         public delegate void TestDelegate(int val);
     4         public event TestDelegate TestEvent;
     5         static void Main(string[] args)
     6         {
     7             Program p = new Program();
     8             p.TestEvent += p.Fun2;
     9             p.TestEvent += Program.Fun1;
    10             p.TestEvent(3);
    11             Console.ReadKey();
    12         }
    13         static void Fun1(int a)
    14         {
    15             Console.Write(a);
    16         }
    17         public void Fun2(int b)
    18         {
    19             Console.Write(b);
    20         }
    21     }

    直接看IL

    主要成员:

    1、名为TestEvent的私有TestDelegate对象
    2、添加事件方法:add_TestEvent(TestDelegate value),参数为TestDelegate类型
    3、删除事件方法:remove_TestEvent(TestDelegate value),参数为TestDelegate类型

    对于添加操作TestEvent+=Fun2实际会做以下操作(删除同理):
    1、获取Fun2引用(同委托获取引用)。
    2、new TestDelegate(),并传入第一步引用为参数。
    3、调用add_TestEvent方法,参数为上一步创建的TestDelegate实例。
    4、在add_TestEvent方法内部,通过调用System.Delegate.Combine(Delegate a, Delegate b)方法,将第二步对象加入TestEvent对象委托列表 

    在上述实例中就是在 Program对象 内部提前创建了一个私有TestDelegate委托对象TestEvent,并对其提供了 添加和删除 TestDelegate对象的方法。

    事件的添加,删除,调用就是对TestEvent对象的添加,删除,调用。

    可以看出 所谓事件只是对委托做了简单的包装。其本质依然是委托。

    对于常用的标准事件的写法 public event EventHandler<EventArgs> TestEvent, 原理也如此,区别只是注册方法的参数不同而已。

  • 相关阅读:
    MAC之基本命令(持续更新)
    Mac下android_sdk配置环境变量
    Eclipse最常用10大快捷键
    Android之使用Jsoup抓取网络数据
    MAC之curl命令
    MAC之cat命令
    Android之FileOutputStream与openFileOutput()的区别
    C# 数字语音wav 提示。。。。。。。。。。。
    HttpWebRequest 获取验证码的图片 并针对有验证码的网页进行Winform登陆。
    经常开车,坐车的朋友请进(看后对你绝对有好处)
  • 原文地址:https://www.cnblogs.com/qingzhuo/p/3919305.html
Copyright © 2011-2022 走看看