C#/.Net的多播委托到底是啥?彻底剖析下
前言
委托在.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的多播委托到底是啥?彻底剖析下的更多相关文章
- 【C#进阶】多播委托和委托数组像是一回事~
这个MathOperation类有三静态方法,参数都是double,并且没有返回值,考虑用Action<>() 这种预定义的委托哦 class MathOperations { publi ...
- 委托Delegate,多播委托和委托链
定义一个委托 public delegate void CalculateDelegate(int 32 x,int 32 y); 定义一个委托类型的变量 public static Calculat ...
- C# 注销掉事件,解决多播委托链表的问题
c#的事件是多播委托.当绑定多个事件时,事件会依次触发,清除掉注册的委托链表:方法1 C# Code 12345678910111213141516171819202122232425262728 ...
- 【小白学C#】谈谈C#多播委托因异常而终止的解决方案
一.前言 前几天,马三在与朋友闲聊技术的时候,朋友忽然抛出一个问题,把马三难倒了,本着求知的精神,回来以后马三就查阅了相关资料并做了一些实验,终于把问题搞明白了,因此写下本篇博客记录一下.首先,问题是 ...
- C# 委托链(多播委托)
委托既可以封装一个方法,又可以对同一类型的方法进行封装,它就是多播委托 using System; using System.Collections.Generic; using System.Lin ...
- 多播委托和匿名方法再加上Lambda表达式
多播委托就是好几个方法全都委托给一个委托变量 代码: namespace 委托 { class Program { static void math1() { Console.WriteLine(&q ...
- ios多播委托
在现实中回调的需求也分两种 一对一的回调. 一对多的回调. 对于一对一的回调,在IOS中使用delegate.block都能实现.而一对多的回调基本就是通知中心了. 假如现在有一个需求,我们以图片下载 ...
- 委托、多播委托(MulticastDelegate)
委托.多播委托(MulticastDelegate) 多播委托(MulticastDelegate)继承自 Delegate ,表示多路广播委托:即,其调用列表中可以拥有多个元素的委托.实际上,我们自 ...
- 委托、Lambda表达式、事件系列04,委托链是怎样形成的, 多播委托, 调用委托链方法,委托链异常处理
委托是多播委托,我们可以通过"+="把多个方法赋给委托变量,这样就形成了一个委托链.本篇的话题包括:委托链是怎样形成的,如何调用委托链方法,以及委托链异常处理. □ 调用返回类型为 ...
- 委托、多播委托、泛型委托Func,Action,Predicate,ExpressionTree
当试图通过一个事件触发多个方法,抽象出泛型行为的时候,或许可以考虑使用委托. 通过委托构造函数或委托变量把方法赋值给委托 private delegate double DiscountDel ...
随机推荐
- 比memcpy还要快的内存拷贝,老哥了解一下?
本文来自博客园,作者:T-BARBARIANS,转载请注明原文链接:https://www.cnblogs.com/t-bar/p/17262147.html 谢谢! 前言 朋友们有想过居然还有比me ...
- 刷爆 LeetCode 周赛 339,贪心 / 排序 / 拓扑排序 / 平衡二叉树
本文已收录到 AndroidFamily,技术和职场问题,请关注公众号 [彭旭锐] 提问. 大家好,我是小彭. 上周末是 LeetCode 第 339 场周赛,你参加了吗?这场周赛覆盖的知识点比较少, ...
- 成为钢铁侠!只需一块RTX3090,微软开源贾维斯(J.A.R.V.I.S.)人工智能AI助理系统
梦想照进现实,微软果然不愧是微软,开源了贾维斯(J.A.R.V.I.S.)人工智能助理系统,贾维斯(jarvis)全称为Just A Rather Very Intelligent System(只是 ...
- 1.springsecurity基于内存和数据库的认证
1.总结: 昨天主要是使用security实现了基于内存的认证和基于数据库的认证(实际项目中使用): 在security的项目中,必须配置WebSecurityConfigurerAdaptor的实现 ...
- Nucleistudio+Vivado协同仿真教程
创建Vivado工程 1.创建工程: 在Vivado中创建工程,命名随意,路径随意: 2.配置工程: 这里可以选择是否添加源文件等,我们先不添加: 3.选择FPGA核心: 选择MCU200T对应的FP ...
- Android Studio中的一些常见控件
Android Studio是一款非常流行的用于开发Android应用程序的集成开发环境(IDE).它提供了许多内置控件,使开发人员可以轻松创建应用程序界面和功能.在本文中,我们将介绍Android ...
- 一款能“干掉” ChatGPT 的应用「GitHub 热点速览」
据说有了它,ChatGPT 就可以靠边站了.因为 Auto-GPT 能更加主动地完成你给他的指定任务,不用做更多的人为干涉,它的推理能力比 ChatGPT 更强,有人用它解放双手做了个 React 网 ...
- 移除List的统一逻辑写法 LeetCode 203
原理:通过创建一个新的结点,放在头结点的前面,作为真正头结点的前驱结点,这样头结点就成为了意义上的非头结点,这样就可以统一操作结点的删除操作. 需要注意的是:这个新的结点是虚拟头结点,真的的头结点依然 ...
- Java的final修饰符
final 实例域 可以将实例域定义为 final.对于 final 域来说,构建对象时必须初始化 final 实例域,构造对象之后就不允许改变 final 实例域的值了.也就是说,必须确保在每一个构 ...
- “StackLLaMA”: 用 RLHF 训练 LLaMA 的手把手教程
如 ChatGPT,GPT-4,Claude语言模型 之强大,因为它们采用了 基于人类反馈的强化学习 (Reinforcement Learning from Human Feedback, RLHF ...