首页 > 其他分享 >.NET Core 委托底层原理浅谈

.NET Core 委托底层原理浅谈

时间:2024-11-13 10:08:53浏览次数:1  
标签:Core 浅谈 委托 int object ._ NET methodPtr target

简介

.NET通过委托来提供回调函数机制,与C/C++不同的是,委托确保回调是类型安全,且允许多播委托。并支持调用静态/实例方法。

简单来说,C++的函数指针有如下功能限制,委托作为C#中的上位替代,能弥补函数指针的不足。

  1. 类型不安全
    函数指针可以指向一个方法定义完全不同的函数。在编译期间不检查正确性。在运行时会导致签名不同导致程序崩溃
  2. 只支持静态方法
    只支持静态方法,不支持实例方法(只能通过邪道来绕过)
  3. 不支持方法链
    只能指向一个方法定义

函数指针与委托的相似之处

函数指针

typedef int (*func)(int, int);

委托

delegate int func(int a, int b);

委托底层模型

image

delegate关键字作为语法糖,IL层会为该关键字自动生成Invoke/BeginInvoke/EndInvoke方法,在.NET Core中,不再支持BeginInvoke/EndInvoke

眼见为实

    public abstract partial class Delegate : ICloneable, ISerializable
    {
        // _target is the object we will invoke on
        internal object? _target; // 源码中的注释不太对(null if static delegate)。应该是这样:如果注册的是实例方法,则是this指针,如果是静态则是delegate实例自己。

        // MethodBase, either cached after first request or assigned from a DynamicMethod
        // For open delegates to collectible types, this may be a LoaderAllocator object
        internal object? _methodBase; //缓存

        // _methodPtr is a pointer to the method we will invoke
        // It could be a small thunk if this is a static or UM call
        internal IntPtr _methodPtr;//实例方法的入口,看到IntPtr关键字就知道要与非托管堆交互,必然就是函数指针了,

        // In the case of a static method passed to a delegate, this field stores
        // whatever _methodPtr would have stored: and _methodPtr points to a
        // small thunk which removes the "this" pointer before going on
        // to _methodPtrAux.
        internal IntPtr _methodPtrAux;//静态方法的入口
    }
    public abstract class MulticastDelegate : Delegate
    {
		//多播委托的底层基石
        private object? _invocationList; 
        private nint _invocationCount;

		//实例委托调用此方法
		private void CtorClosed(object target, IntPtr methodPtr)
        {
            if (target == null)
                ThrowNullThisInDelegateToInstance();
            this._target = target;
            this._methodPtr = methodPtr;//函数指针被指向_methodPtrAux
        }
		//静态委托调用此方法
		private void CtorOpened(object target, IntPtr methodPtr, IntPtr shuffleThunk)
        {
            this._target = this;//上面说到,_target的注释不对的判断就在此
            this._methodPtr = shuffleThunk;//与实例委托不同,这里被指向一个桩函数
            this._methodPtrAux = methodPtr;//函数指针被指向_methodPtrAux
        }
    }
	

委托如何同时支持静态方法与实例方法?

示例代码
        static void Main(string[] args)
        {
            //1.注册实例方法
            MyClass myObject = new MyClass();
            MyDelegate myDelegate2 = new MyDelegate(myObject.InstanceMethod);
            myDelegate2.Invoke("Hello from instance method");

            Debugger.Break();

            //2.注册静态方法
            MyDelegate myDelegate = MyClass.StaticMethod;
            myDelegate.Invoke("Hello from static method");

            Debugger.Break();

        }
    }

    public delegate void MyDelegate(string message);

    public class MyClass
    {
        public static void StaticMethod(string message)
        {
            Console.WriteLine("Static Method: " + message);
        }

        public void InstanceMethod(string message)
        {
            Console.WriteLine("Instance Method: " + message);
        }
    }
            myDelegate2.Invoke("Hello from instance method");
00007ff9`521a19bd 488b4df0        mov     rcx,qword ptr [rbp-10h]
00007ff9`521a19c1 48baa0040000c7010000 mov rdx,1C7000004A0h ("Hello from instance method")
00007ff9`521a19cb 488b4908        mov     rcx,qword ptr [rcx+8]
00007ff9`521a19cf 488b45f0        mov     rax,qword ptr [rbp-10h]
00007ff9`521a19d3 ff5018          call    qword ptr [rax+18h] //重点
00007ff9`521a19d6 90              nop   
            myDelegate.Invoke("Hello from static method");
00007ff9`521a1a54 488b4de8        mov     rcx,qword ptr [rbp-18h]
00007ff9`521a1a58 48baf0040000c7010000 mov rdx,1C7000004F0h ("Hello from static method")
00007ff9`521a1a62 488b4908        mov     rcx,qword ptr [rcx+8]
00007ff9`521a1a66 488b45e8        mov     rax,qword ptr [rbp-18h]
00007ff9`521a1a6a ff5018          call    qword ptr [rax+18h]  //重点
00007ff9`521a1a6d 90              nop

可以看到,静态与实例都指向了rax+18h的地址偏移量。那么+18到底指向哪里呢?
image

Invoke的本质就是调用_methodPtr所在的函数指针

那么有人就会问了,前面源码里不是说了。静态方法的入口不是_methodPtrAux吗?怎么变成_methodPtr了。
实际上,如果是静态委托。JIT会生成一个桩方法,桩方法内部调用会+20偏移量的内容。从而调用_methodPtrAux

实例与静态核心代码的差异,大家有兴趣的话可以看一下它们的汇编

  1. 实例方法核心代码
private void CtorClosed(object target, nint methodPtr)
{
	if (target == null)
	{
		ThrowNullThisInDelegateToInstance();
	}
	_target = target;
	_methodPtr = methodPtr;//_methodPtr真正承载了函数指针
}

  1. 静态方法核心代码
private void CtorOpened(object target, nint methodPtr, nint shuffleThunk)
{
	_target = this;
	_methodPtr = shuffleThunk;//_methodPtr只是一个桩函数
	_methodPtrAux = methodPtr;//真正的指针在_methodPtrAux中
}

委托如何支持类型安全?

点击查看代码
    internal class Program
    {
        static void Main(string[] args)
        {
            //1. 编译器层面错误
            //var myDelegate = new MyDelegate(Math.Max);

            //2. 运行时层类型转换错误
            var myDelegate = new MyDelegate(Console.WriteLine);
            MyMaxDelegate myMaxDelegate = (MyMaxDelegate)(object)myDelegate;

            Debugger.Break();
        }

        public delegate void MyDelegate(string message);
        public delegate int MyMaxDelegate(int a, int b);


    }
  1. 编译器层会拦截
    这个很简单,在编译器中如果定义不匹配就会报错。
    image

  2. CLR Runtime会在汇编中插入检查命令
    检查不一致会报错,不至于整个程序奔溃。
    image

委托如何支持多播?

image

多播委托的添加

委托使用+=或者Delegate.Combine来添加新的委托。其底层调用的是CombineImpl,由子类MulticastDelegate实现。
并最终产生一个新的委托

for循环1000次Combine委托,会产生1000个对象,

		//简化版
        protected sealed override Delegate CombineImpl(Delegate? follow)
        {
            MulticastDelegate dFollow = (MulticastDelegate)follow;
            object[]? resultList;
            int followCount = 1;
            object[]? followList = dFollow._invocationList as object[];
            if (followList != null)
                followCount = (int)dFollow._invocationCfollowListount;

            int resultCount;
			            if (!(_invocationList is object[] invocationList))
            {
                resultCount = 1 + followCount;
                resultList = new object[resultCount];
                resultList[0] = this;
                if (followList == null)
                {
                    resultList[1] = dFollow;
                }
                else
                {
                    for (int i = 0; i < followCount; i++)
                        resultList[1 + i] = followList[i];
                }
                return NewMulticastDelegate(resultList, resultCount);
            }
			//xxxxxxxxxx
        }
		//关键核心,将组合后的Delegate组成一个新对象,并填充invocationList,invocationCount
        private MulticastDelegate NewMulticastDelegate(object[] invocationList, int invocationCount, bool thisIsMultiCastAlready)
        {
            // First, allocate a new multicast delegate just like this one, i.e. same type as the this object
            MulticastDelegate result = (MulticastDelegate)InternalAllocLike(this);

            // Performance optimization - if this already points to a true multicast delegate,
            // copy _methodPtr and _methodPtrAux fields rather than calling into the EE to get them
            if (thisIsMultiCastAlready)
            {
                result._methodPtr = this._methodPtr;
                result._methodPtrAux = this._methodPtrAux;
            }
            else
            {
                result._methodPtr = GetMulticastInvoke();
                result._methodPtrAux = GetInvokeMethod();
            }
            result._target = result;
            result._invocationList = invocationList;
            result._invocationCount = invocationCount;

            return result;
        }

多播委托的执行

上面提到,Invoke的本质就是调用_methodPtr所在的函数指针.
那么自然而然,负责执行多播肯定就是_methodPtr了。
从上面的源码可以知道,MulticastDelegate在初始化的时候要调用一次GetMulticastInvoke(),让我们来看看它是什么?
image

哦豁,它还是一个非托管的方法,有兴趣的同学可以自行查看coreclr的c++源码。奥秘就在其中,本人水平有限,怕误人子弟。

简单来说,就是_methodPtr方法在coreclr底层,for循环执行invocationList的委托队列。

思考一个问题,如果只是一个简单的for循环,其中一个委托卡死/执行失败,怎么办?
提示:MulticastDelegate类中有很多override method

非托管委托(函数指针)

C#作为C++的超集,也别名为C++++ 。也可以说是C++的手动挡(JAVA是C++的自动挡)。
自然而然,C++有的,C#也要有。因此在C#11中引入了函数指针,性能更强的同时也继承了C++的所有缺点(除了会在编译期间协助类型安全检查).
https://learn.microsoft.com/zh-cn/dotnet/csharp/language-reference/unsafe-code#function-pointers

泛型委托

为了减轻你工作量,避免创建太多委托定义。BCL提供了Action/Func来提供便利,减少你的代码量。
image

它们在底层与delegate并无区别
image

Lambd表达式

泛型委托是Lambd的基石

事件与委托的关系

CLR事件模型以委托为基础,它们之间的关系。像是对委托的进一步封装。

  1. 事件就是一个语法糖,自己自身并没有新概念
  2. 委托和事件的关系”等同于“字段和属性的关系”

image
事件作为语法糖,IL会在底层生成一个委托并提供add_xxxx与remove_xxxx方法对委托进行封装。
image
实际上在底层,还是操作Delegate.Combine那一套东西

标签:Core,浅谈,委托,int,object,._,NET,methodPtr,target
From: https://www.cnblogs.com/lmy5215006/p/18534896

相关文章

  • 检查电脑的 .net framework 的版本(复制到powershell里执行)
    打开powershell,$release=Get-ItemPropertyValue-LiteralPath'HKLM:SOFTWARE\Microsoft\NETFrameworkSetup\NDP\v4\Full'-NameReleaseswitch($release){{$_-ge533320}{$version='4.8.1orlater';break}{$_-ge52......
  • .NET周刊【11月第2期 2024-11-10】
    国内文章.NET全能高效的CMS内容管理系统https://www.cnblogs.com/1312mn/p/18511224SSCMS是一个完全开源的企业级内容管理系统,基于.NETCore开发,适合跨平台部署。其特点包括支持多终端发布和功能插件,具有完善的权限控制和安全机制,可通过标签和API接口进行二次开发。SSC......
  • MAUI Blazor学习20-升级到Net8
    MAUIBlazor学习20-升级到Net8 MAUIBlazor系列目录MAUIBlazor学习1-移动客户端Shell布局-SunnyTrudeau-博客园(cnblogs.com)MAUIBlazor学习2-创建移动客户端Razor页面-SunnyTrudeau-博客园(cnblogs.com)MAUIBlazor学习3-绘制ECharts图表-SunnyTrudeau-博......
  • 浅谈贪心算法
    浅谈贪心算法贪心算法,指在问题求解时,每一步都做出“当前看起来最好的决策”。它没有固定的算法模板,灵活性强。在OI领域,无论是入门组,还是省选,NOI,或多或少都出过贪心题。可见贪心的重要性之大。使用贪心算法解决问题,必须满足“无后效性”。满足“无后效性”不一定当前的决策......
  • error: NU1100: 无法解析 net8.0 的“System.Management.Automation (>= 7.2.0)”。
    前言最近,在使用Net调用PowerShell,碰到了一个很不常见的错误,记录一下,也许有朋友会遇到,希望有所帮助。正文错误截图如下,其实很奇怪,一样的代码,有些地方报错,有些没事。2.文字版本的错误,方便复制粘贴,如下:MicrosoftWindows[版本10.0.22000.2538](c)Micr......
  • Kubernetes基础——Kubectl指令原理
    一、查看指令1、查看所有namespaces下的pods信息kubectlgetpods-Akubectlgetpods-nkube-system2、查看所有pods的configmap文件kubectlgetcm-Akubectlgetcm-nkube-system 3、查看没有命名空间的资源kubectlapi-resources--namespaced=false4、查看使用......
  • AFPN: Asymptotic Feature Pyramid Network for Object Detection-afpn
    paper可以借鉴的点:下采样和上次样融合两个不同尺度特征图fromcollectionsimportOrderedDictimporttorchimporttorch.nnasnnimporttorch.nn.functionalasFdefBasicConv(filter_in,filter_out,kernel_size,stride=1,pad=None):ifnotpad:p......
  • .NET 公共语言运行时(Common Language Runtime,CLR)
    .NET的公共语言运行时(CommonLanguageRuntime,CLR)是.NETFramework和.NETCore的核心组件,负责运行和管理.NET程序。CLR提供了一个高效、安全和稳定的执行环境,支持多种编程语言并处理各种系统级的任务。下面是对.NETCLR的详细介绍,包括其功能、架构、以及如何与.NET应......
  • AutoCAD Blockview .net在wpf项目中的问题
    之前使用Blockview是遇到平移的问题,这几天在学习使用CommunityToolkit.MVVM框架来创建用户界面,当创建GsPreviewCtrl控件时会遇到错误,导致整个窗体不能显示,错误信息如下:**************异常文本**************System.InvalidProgramException:公共语言运行时检测到无效的......
  • 浅谈python回归算法及其应用
    Python中有很多常用的回归算法,可以用于解决不同的问题。以下是几种常见的回归算法及其应用:1.线性回归:线性回归是一种最简单的回归算法,用于建立自变量和因变量之间的线性关系。它可以用于预测房价、销售量等连续变量。2.多项式回归:多项式回归允许自变量与因变量之间的非线......