前言

委托在.Net里面被托管代码封装了之后,看起来似乎有些复杂。但是实际上委托即是函数指针,而多播委托,即是函数指针链。本篇来只涉及底层的逻辑,慎入。

概括

1.示例代码

public delegate void ABC(); //委托写在类的外面
public class Test
{
public ABC AAA;
public void A() { }
public void B() { }
}
static void Main(string[] args)
{
Test test = new Test();
test.AAA += new ABC(test.A);
test.AAA += new ABC(test.B);
test.AAA(); //test.AAA.Invoke();
}

以上的test.AAA+=的等号后面每放一个函数,就相当于多了一个函数指针。号称:多播委托。

2.多播原理伪代码

以上委托可以简化成以下伪代码,其它所有多播委托均可依次类推。

int i;// i表示多播委托的次数
if(i==1) //也就是只test.AAA += new ABC(test.A);然后调用test.AAA()
{
test.A() //只有一个多播,直接调用这一个函数
}
else // 如果大于一个多播委托,如示例两个多播
{
IntPtr FunPtr=test.A()+test.B(); //函数A和函数B形成了一个新的托管地址
FunPtr();//在新形成的托管地址里面分别调用函数A和函数B
}

3.内存模型

对象(object)的内存,大致是:

为了简洁,实质非常庞大
header+MethodTable+field

委托根据对象来,以示例代码的test对象为例,test对象有一个filed也即是委托类型的变量AAA。AAA则是new ABC得来的。new ABC所实例化对象的filed是分别为函数A,B。那么他们的内存模型如下所示:

test==header+Mehtodtalbe + AAA(test.AAA(1) or test.AAA(2)+test.AAA(1))
test.AAA(1)==new ABC(test.A):header+Methodtable+函数A(precode)
test.AAA(2)==new ABC(test.B):header+Methodtable+函数B(precode)

特例:当只有一个多播委托(多播伪代码里的i==1),类似于以下这种情况:

如果:
static void Main(string[] args)
{
Test test = new Test();
test.AAA += new ABC(test.A);//只有一个多播
test.AAA(); //test.AAA.Invoke();
}
那么:
test==header+Mehtodtalbe + AAA(test.AAA(1))
test.AAA(1)==new ABC(test.A)(header+Methodtable+函数A(precode,offset:0x18)) 内存:
0x000001DB38D552C0 00007ffa3b3654d8 000001db38d55858
这里的0x000001DB38D552C0即test的MethodTable地址。
000001db38d55858即new ABC(test.A)的MethodTable地址

委托里面只有一个方法test.A(多播伪代码里的i==1),这种情况的话,JIT会直接寻找test.AAA(1)的MethodTable,加上偏移位0x18,也即是函数test.A的函数地址。然后运行。

注意了,因为对象test只有一个filed:AAA。超过一个以上的多播(多播伪代码里的i!=1,也即else逻辑),它的field是一直变化的,比如new ABC(test.A)的时候,它的filed是test.AAA(1)。而new ABC(test.B)的时候,它的field则是test.AAA(2)+test.AAA(1)组合成的托管函数,覆盖掉前面的。如果有test.AAA(3),那么后面继续组合,继续覆盖test对象的field。

当它组合之后,形成一个新的地址,CLR会在这个地址的基础上加上偏移量0x18(同上特例)进行托管函数代码调用。JIT Compile之后,在里面分别调用函数test.A,test.B,完成委托的多播。

参照如下代码:

test.AAA(); //test.AAA.Invoke();
00007FFA3AFF7A27 mov rcx,qword ptr [rbp+28h]
00007FFA3AFF7A2B mov rcx,qword ptr [rcx+8]
00007FFA3AFF7A2F mov rax,qword ptr [rbp+28h]
00007FFA3AFF7A33 call qword ptr [rax+18h]
00007FFA3AFF7A36 nop

4.托管和非托管

依次调用顺序,以下函数按照顺序在多播委托中调用:

托管:

System.MulticastDelegate:CtorClosed //把对象test对象的field设置为abc
System.Delegate:Combine //组合成新的委托,也即函数指针链,如果只有一个多播,则即那一个函数指针
System.Runtime.CompilerServices.CastHelpers.ChkCastClass //进行类型转换

非托管:

JIT_WriteBarrier //设置card_table,防止GC标记的时候漏掉

5.原理图

多播委托原理如下图所示:



单个委托实际上就是调用函数指针,而多个委托,则是通过多播委托组合单个委托形成一个新的托管函数,在这个托管函数里面进行单个函数一一调用。

结尾

作者:江湖评谈

关注公众号:jianghupt。后台回复:dotnet7。获取一套.Net7 CLR源码教程。

C#/.Net的多播委托到底是啥?彻底剖析下的更多相关文章

  1. 【C#进阶】多播委托和委托数组像是一回事~

    这个MathOperation类有三静态方法,参数都是double,并且没有返回值,考虑用Action<>() 这种预定义的委托哦 class MathOperations { publi ...

  2. 委托Delegate,多播委托和委托链

    定义一个委托 public delegate void CalculateDelegate(int 32 x,int 32 y); 定义一个委托类型的变量 public static Calculat ...

  3. C# 注销掉事件,解决多播委托链表的问题

    c#的事件是多播委托.当绑定多个事件时,事件会依次触发,清除掉注册的委托链表:方法1  C# Code  12345678910111213141516171819202122232425262728 ...

  4. 【小白学C#】谈谈C#多播委托因异常而终止的解决方案

    一.前言 前几天,马三在与朋友闲聊技术的时候,朋友忽然抛出一个问题,把马三难倒了,本着求知的精神,回来以后马三就查阅了相关资料并做了一些实验,终于把问题搞明白了,因此写下本篇博客记录一下.首先,问题是 ...

  5. C# 委托链(多播委托)

    委托既可以封装一个方法,又可以对同一类型的方法进行封装,它就是多播委托 using System; using System.Collections.Generic; using System.Lin ...

  6. 多播委托和匿名方法再加上Lambda表达式

    多播委托就是好几个方法全都委托给一个委托变量 代码: namespace 委托 { class Program { static void math1() { Console.WriteLine(&q ...

  7. ios多播委托

    在现实中回调的需求也分两种 一对一的回调. 一对多的回调. 对于一对一的回调,在IOS中使用delegate.block都能实现.而一对多的回调基本就是通知中心了. 假如现在有一个需求,我们以图片下载 ...

  8. 委托、多播委托(MulticastDelegate)

    委托.多播委托(MulticastDelegate) 多播委托(MulticastDelegate)继承自 Delegate ,表示多路广播委托:即,其调用列表中可以拥有多个元素的委托.实际上,我们自 ...

  9. 委托、Lambda表达式、事件系列04,委托链是怎样形成的, 多播委托, 调用委托链方法,委托链异常处理

    委托是多播委托,我们可以通过"+="把多个方法赋给委托变量,这样就形成了一个委托链.本篇的话题包括:委托链是怎样形成的,如何调用委托链方法,以及委托链异常处理. □ 调用返回类型为 ...

  10. 委托、多播委托、泛型委托Func,Action,Predicate,ExpressionTree

    当试图通过一个事件触发多个方法,抽象出泛型行为的时候,或许可以考虑使用委托.     通过委托构造函数或委托变量把方法赋值给委托 private delegate double DiscountDel ...

随机推荐

  1. ggplot2图形可视化应用集锦

    数据可视化就是将我们从数据中探索的信息与图形要素对应起来的过程.数据可视化,先要理解数据,再去掌握可视化的方法,这样才能实现高效的数据可视化.数据可视化技术的基本思想,是将数据库中每一个数据项作为单个 ...

  2. [软件工程/数据工程] 软件工程&数据工程知识体系

    1 概述 本篇是为了重新总结.重新编写5年前(2018-12-31 00:06),临近毕业时的一篇文章软件工程专业知识体系[求职/就业]而作,至此篇文章发布时,原文文章应已被删除.但第1章节中仍会存在 ...

  3. JavaScript的引入方式

    外部JS文件 deno.js alert('你好!JavaScript'); JS引入方式.html <!--方式一:内部脚本--> <!--标签不能自闭和--> <sc ...

  4. Springboot集成MongoDB存储文件、读取文件

    一.前言和开发环境及配置 可以转载,但请注明出处. 之前自己写的SpringBoot整合MongoDB的聚合查询操作,感兴趣的可以点击查阅. https://www.cnblogs.com/zaoyu ...

  5. vue3+vant创建移动端项目,实战项目常见采坑记录

    前言: 产品背景介绍 我所做的这个项目,刚开始是没有移动端需求的,等PC端做完了上线使用了几个月后,突然有一天产品经理找到我说是要做一个在PC端添加一个快速注册入口,用手机微信扫二位码进入移动端注册页 ...

  6. 手写 HashSet的底层 和 迭代器

    1 package Test.CollectionIterator; 2 import java.util.Iterator; 3 public class MyHashSet2<E> i ...

  7. LangChain vs Semantic Kernel

    每当向他人介绍 Semantic Kernel, 会得到的第一个问题就是 Semantic Kernel 类似于LangChain吗,或者是c# 版本的LangChain吗? 为了全面而不想重复的回答 ...

  8. javasec(一)java反射

    这篇文章介绍javasec基础知识--java反射. 0x01 反射是什么? 反射是一种机制,利用反射机制动态的实例化对象.读写属性.调用方法.构造函数. 在程序运行状态中,对于任意一个类或对象,都能 ...

  9. 访问nginx报错502日志:failed (13: Permission denied) while connecting to upstream

    1.错误问题 nginx启动成功,但是访问nginx报错502.检查后台项目,使用IP+端口可以正常访问项目的,这说明项目启动成功了.那就是nginx的问题.检查了nginx.conf文件发现配置的反 ...

  10. [C++提高编程] 3.1 string容器

    文章目录 3.1 string容器 3.1.1 string基本概念 3.1.2 string构造函数 3.1.3 string赋值操作 3.1.4 string字符串拼接 3.1.5 string查 ...