前言

委托在.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. Git链接上游仓库

    技术背景 在Git的操作过程中,一般的组织内部工作模式可以在同一个仓库上的master-develop-feature不同分支上进行开发,也有一些人和外部协作者会通过Fork到自己本地的仓库进行更新的 ...

  2. kubernetes(k8s)中部署 efk

    Kubernetes 开发了一个 Elasticsearch 附加组件来实现集群的日志管理.这是一个 Elasticsearch.Fluentd 和 Kibana 的组合. Elasticsearch ...

  3. 深度学习之PyTorch实战(5)——对CrossEntropyLoss损失函数的理解与学习

    其实这个笔记起源于一个报错,报错内容也很简单,希望传入一个三维的tensor,但是得到了一个四维. RuntimeError: only batches of spatial targets supp ...

  4. 【LeetCode动态规划#06】分割等和子集(01背包问题一维写法实战)

    分割等和子集 分割等和子集 给你一个 只包含正整数 的 非空 数组 nums .请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等. 示例 1: 输入:nums = [1,5,11,5 ...

  5. Semantic Kernel 入门系列:💬Semantic Function

    如果把提示词也算作一种代码的话,那么语义技能所带来的将会是全新编程方式,自然语言编程. 通常情况下一段prompt就可以构成一个Semantic Function,如此这般简单,如果我们提前可以组织好 ...

  6. Spring源码系列:初探底层,手写Spring

    前言 在学习Spring框架源码时,记住一句话:源码并不难,只需要给你各种业务场景或者项目经理,你也能实现自己的Spring.虽然你的实现可能无法与开源团队相媲美,但是你肯定可以实现一个0.0.1版本 ...

  7. 使用 LoRA 和 Hugging Face 高效训练大语言模型

    在本文中,我们将展示如何使用 大语言模型低秩适配 (Low-Rank Adaptation of Large Language Models,LoRA) 技术在单 GPU 上微调 110 亿参数的 F ...

  8. Appweb交叉编译

    Appweb交叉编译 编译环境:ubuntu-12.04 x64 开发平台:Hi3535 arm版 编译版本:appweb-6.1.1.zip 下载地址=> Appweb web site: h ...

  9. 如何利用Requestly提升前端开发与测试的效率,让你事半功倍?

    痛点 前端测试 在进行前端页面开发或者测试的时候,我们会遇到这一类场景: 在开发阶段,前端想通过调用真实的接口返回响应 在开发或者生产阶段需要验证前端页面的一些 异常场景 或者 临界值 时 在测试阶段 ...

  10. 几种常见的Python数据结构

    摘要:本文主要为大家讲解在Python开发中常见的几种数据结构. 本文分享自华为云社区<Python的常见数据结构>,作者: timerring . 数据结构和序列 元组 元组是一个固定长 ...