委托的内部机制

 

     委托是一种定义方法签名的类型,是对方法的抽象、封装。与委托的签名(由返回类型和参数组成)匹配的任何可访问类和结构中的任何方法都可以分配给该委托,方法可以是静态方法,也可以是实例方法。将一个方法绑定到委托时,C#和CLR允许引用类型的协变性逆变性

协变性是指方法的返回类型可以派生自委托的返回类型。

逆变性是指委托的参数类型可以派生自方法的参数类型。

协变性和逆变性只能用于引用类型,不能用于值类类型或void。所以不能将下面的方法与委托绑定:

复制代码
        delegate Object DelegateCallBack(FileStream s);
        public int CallBackMethod(Stream s)
        {
            //dosomthing
            return 1;
        }
复制代码

  而下面的方法是可以进行绑定的:

        public string CallBackMethod(Stream s)
        {
            //dosomthing
            return "1";
        }

委托具有以下特点:

1.委托类似于C++函数指针,但它们是类型安全的。

2.委托允许将方法作为参数进行传递。

3.委托可以用于定义回调方法。

4.委托可以链接在一起;例如,可以对一个事件调用多个方法。

5.方法不必与委托签名完全匹配。

      通过反编译工具可以知道委托类型会被编译成一个类(一个sealed类,所以不允许从委托类型派生任何类型),并且这个类继承自System.MulticastDelegate类(所有委托类型都派生自该类型)。委托继承了MulticastDelegate类的字段、属性和方法,其中三个重要的对于理解委托及委托链的内部机制非常有帮助的是_target, _methodPtr, _invocationList。具体解释一下:

_target:当委托对象包装一个静态方法时,该值为null。包装实例方法时,该值是回调方法要做操的对象。

_methodPtr:一个内部的整数值,标示要回调的方法。

_invocationList:委托链时,引用一个委托数组,否则为null。

下面通过具体的代码,调试查看上述三个字段的实际赋值情况:

回调静态方法:

复制代码
        public delegate void DelegateSample(string content);
        static void Main(string[] args)
        {            DelegateSample staticInstance = SendMessage;
            Console.ReadLine();
        }
        public static void SendMessage(string content)
        {
            Console.WriteLine(content);
        }
复制代码

调试可以发现:

_target并不是为null,而是引用的方法信息,为什么呢?这个我暂时也不知道。但是Target属性的值为null,没有问题,这个属性分装了字段_target,回调静态方法时为null。

回调实例方法:

复制代码
        static void Main(string[] args)
        {
            DelegateSample x = new Program().SendQQ;
            Console.ReadLine();
        }
        public void SendQQ(string content)
        {
            Console.WriteLine(content);
        }
复制代码

调试查看委托实例x:

委托链:

        稍微修改一下上面的例子,哎,这个例子改的我自己都快受不了了。

复制代码
        static void Main(string[] args)
        {
            DelegateSample x = new Program().SendMessage;
            DelegateSample sample = new DelegateSample(new Program().SendQQ);
            sample += SendEmial;
            sample += new Program().SendMessage;
            DelegateSample s = SendEmial;//与 sample 链中SendEmial同一个对象
            sample.Invoke("通知");
            new Program().SendNotice("QQ通知:*****", x);
            Console.ReadLine();
        }
复制代码

调试查看委托实例x:可以发现_invocationList被初始化为引用一个委托数组。

委托实例x:

委托链数组的第三个元素与委托实例x相同,实际上这两个委托都是方法SendMessage的引用,所以截图中的数值相同也就不大惊小怪了,既然是同一个方法的引用,那么函数的入口地址就应该相同。

另外一点:在对委托经行+=操作时,当发现当前委托变量已经引用了委托对象时,会新构造一个委托对象,并对新对象进行初始化,最后设置委托变量引用新的委托对象,之前的委托对象以及_invocationList字段引用的委托数组将会被当做垃圾某个时刻回收,-=操作同样会新建委托对象。

 总结:

1.声明委托的本质是声明一个代表着某一系列方法的类。

2.委托链就是一个委托,其内部含有一个委托对象数组。

3.调用委托的本质是调用实例化委托对象中的Invoke方法,委托链则是遍历其委托对象数组,依次调用数组中的方法。

原文地址:https://www.cnblogs.com/wfy680/p/12380730.html