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

.NET Core 反射底层原理浅谈

时间:2024-11-15 09:33:50浏览次数:1  
标签:Core 浅谈 反射 私有 Person BindingFlags 类型 NET Emit

简介

反射,反射,程序员的快乐。

前期绑定与后期绑定

在.NET中,前期绑定(Early Binding)是指在编译时就确定了对象的类型和方法,而后期绑定(Late Binding)或动态绑定是在运行时确定对象的类型和方法。

前置知识:C#类型系统结构

C#作为C++++ ,在类型系统上沿用C++的类型系统

前期绑定

在代码能执行之前,将代码中依赖的assembly,module,class,method,field等类型系统的元素提前构建好。
前期绑定的优点是编译时类型检查,提高了类型安全性和性能。缺点是如果需要更换类型,需要重新编译代码。灵活性不够
image

比如一个简单的的控制台,就自动提前加载了各种需要的DLL文件。完成前期绑定。

后期绑定

后期绑定的优点是可以在运行时更改类型,无需重新编译代码。缺点是在编译时不进行类型检查,可能导致运行时错误。
几个常用的场景,比如dynamic ,多态,System.Reflection等

举个例子,使用Reflection下的“元数据查询API”,动态加载DLL

            var dllpath = "xxx.dll";
            Assembly assembly = Assembly.LoadFrom(dllpath);//构建Assembly+Module

            Type dataAccessType = assembly.GetType("xxxxx");//构建Class(MethodTable+EEClass)

            object dataAccess = Activator.CreateInstance(dataAccessType);//在托管堆中创建MT实例

            MethodInfo addMethod = dataAccessType.GetMethod("Add");//构建MethodDesc
			
            addMethod.Invoke(dataAccess, new object[] { "hello world" });//调用方法

反射

反射的本质就是“操作元数据”

什么是元数据?

MetaData,本是上就是存储在dll中的一个信息数据库,记录了这个assembled中有哪些方法,哪些类,哪些属性等等信息
image
可以看到,各种Table组成的信息,是不是类似一个数据库?

举个例子:
执行Type.GetType("int"),反射会在MetaData寻找"int"的类型。但在运行时会返回null.因为MetaData中只有"System.Int32"这个字符串。

反射如何查询MetaData?

通过Reflection XXXInfo系列API 查询所有细节

Type t = typeof(System.IO.FileStream);
FieldInfo[] fi = t.GetFields(BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public);
PropertyInfo[] pi = t.GetProperties(BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public);
EventInfo[] ei = t.GetEvents(BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public);
......

反射如何构建类型系统

通过Reflection XXXBuilder系列API 构建一个全新的类型

            AssemblyBuilder ab = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName("MyAssembly"), AssemblyBuilderAccess.RunAndCollect);//创建Assembly
            ModuleBuilder mob = ab.DefineDynamicModule("MyModule");//创建Module
            TypeBuilder tb = mob.DefineType("MyType", TypeAttributes.Public | TypeAttributes.Class);//创建Class
            MethodBuilder mb = tb.DefineMethod("SumMethod", MethodAttributes.Public | MethodAttributes.Static, typeof(int), new Type[] { typeof(int), typeof(int) });//创建MethodTable

            ILGenerator il = mb.GetILGenerator();//通过IL API 动态构建MethodDesc
            il.Emit(OpCodes.Ldarg_0);
            il.Emit(OpCodes.Ldarg_1);
            il.Emit(OpCodes.Add);
            il.Emit(OpCodes.Ret);

            Type type = tb.CreateType();  //mt + eeclass

            MethodInfo method = type.GetMethod("SumMethod");
            Console.WriteLine(method.Invoke(null, new object[] { 5, 10 }));

反射底层调用

C#的类型系统,与C++的类型系统是一一对应的。因此其底层必定是调用C++的方法。
示意图如下,有兴趣的小伙伴可以去参考coreclr的源码
image

眼见为实,以Invoke为例

image

反射到底慢在哪?

  1. 动态解析
    从上面可知道,反射作为后期绑定,在runtime中要根据metadata查询出信息,严重依赖字符串匹配,这本身就增加了额外的操作
  2. 动态调用
    使用反射调用方法时,先要将参数打包成数组,再解包到线程栈上。又是额外操作。
  3. 无法在编译时优化
    反射是动态的临时调用,JIT无法优化。只能根据代码一步一步执行。
  4. 额外的安全检查
    反射会涉及到访问和修改只读字段等操作,运行时需要进行额外的安全性检查,这也会增加一定的开销
  5. 缓存易失效
    反射如果参数发生变化,那么缓存的汇编就会失效。又需要重新查找与解析。

总之,千言万语汇成一句话。最好的反射就是不要用反射。除非你能保证对性能要求不高/缓存高命中率

CLR的对反射的优化

除了缓存反射的汇编,.NET 中提供了一系列新特性来尽可能的绕开“反射”

Emit

Emit 是 .NET 提供的一种动态生成和编译代码的技术。通过 Emit,我们可以动态生成一个新的方法,这个方法可以直接访问私有成员,这对于一些特殊场景非常有用,比如动态代理、代码生成器、AOP(面向切面编程)等.

public class Person
{
    private int _age;
    public override string ToString()
    {
        return _age.ToString();
    }
}
static void EmitTest(Person person)
{
    // 获取Person类的类型对象
    Type personType = typeof(Person);

    // 获取私有字段_age的FieldInfo,无法避免部分使用反射
    FieldInfo ageField = personType.GetField("_age", BindingFlags.Instance | BindingFlags.NonPublic);

    if (ageField == null)
    {
        throw new ArgumentException("未找到指定的私有字段");
    }

    // 创建一个动态方法
    DynamicMethod dynamicMethod = new DynamicMethod("SetAgeValue", null, new Type[] { typeof(Person), typeof(int) }, personType);

    // 获取IL生成器
    ILGenerator ilGenerator = dynamicMethod.GetILGenerator();

    // 将传入的Person对象加载到计算栈上(this指针)
    ilGenerator.Emit(OpCodes.Ldarg_0);

    // 将传入的新值加载到计算栈上
    ilGenerator.Emit(OpCodes.Ldarg_1);

    // 将新值存储到对应的私有字段中
    ilGenerator.Emit(OpCodes.Stfld, ageField);

    // 返回(因为方法无返回值,这里只是结束方法执行)
    ilGenerator.Emit(OpCodes.Ret);

    // 创建委托类型,其签名与动态方法匹配
    Action<Person, int> setAgeAction = (Action<Person, int>)dynamicMethod.CreateDelegate(typeof(Action<Person, int>));

    // 通过委托调用动态生成的方法来修改私有字段的值
    setAgeAction(person, 100);
}

切构建代码又臭又长。

Expression

Expression 是 .NET 提供的一种表达式树的技术。通过 Expression,我们可以创建一个表达式树,然后编译这个表达式树,生成一个可以访问私有成员的方法

static void ExpressionTest(Person person)
{
    // 获取Person类的类型对象
    Type personType = typeof(Person);

    // 获取私有字段_age的FieldInfo,无法避免部分使用反射
    FieldInfo ageField = personType.GetField("_age", BindingFlags.Instance | BindingFlags.NonPublic);

    if (ageField == null)
    {
        throw new ArgumentException("未找到指定的私有字段");
    }

    // 创建参数表达式,对应传入的Person对象实例
    ParameterExpression instanceParam = Expression.Parameter(personType, "instance");

    // 创建参数表达式,对应传入的新值
    ParameterExpression newValueParam = Expression.Parameter(typeof(int), "newValue");

    // 创建一个赋值表达式,将新值赋给私有字段
    BinaryExpression assignExpression = Expression.Assign(Expression.Field(instanceParam, ageField), newValueParam);

    // 创建一个包含赋值表达式的表达式块,这里因为只有一个赋值操作,所以块里就这一个表达式
    BlockExpression blockExpression = Expression.Block(assignExpression);

    // 创建一个可执行的委托,其类型与表达式块的逻辑匹配
    Action<Person, int> setAgeAction = Expression.Lambda<Action<Person, int>>(blockExpression, instanceParam, newValueParam).Compile();

    // 通过委托调用表达式树生成的逻辑来修改私有字段的值
    setAgeAction(person, 100);
}

切构建代码又臭又长。

UnsafeAccessorAttribute

.Net 8中引入了新特性UnsafeAccessorAttribute 。
使用该特性,来提供对私有字段的快速修改

static void New()
{
    var person = new Person();
    GetAgeField(person) = 100;
}
[UnsafeAccessor(UnsafeAccessorKind.Field, Name = "_age")]
static extern ref int GetAgeField(Person counter);

为什么它这么快?

对于C#来说,私有类型是OOP语言的定义。它定义了什么是私有类型,它的行为是什么。
但对于程序本身来说,代码和数据都只是一段内存,实际上你的指针想访问哪就访问哪。哪管你什么私有类型。换一个指向地址不就得了。因此CLR开放了这么一个口子,利用外部访问直接操作内存。看它的命名UnsafeAccessor就能猜到意图了

3,2,1. 上汇编!!!
image
直接将rax寄存器偏移量+8,直接返回int(占用4字节,偏移量8)类型的_age。 没有Emit,Expression的弯弯绕绕,丝毫不拖泥带水。

.NET 9中的改进

支持泛型,更优雅。
https://learn.microsoft.com/zh-cn/dotnet/core/compatibility/core-libraries/9.0/unsafeaccessor-generics

参考资料

https://blog.csdn.net/sD7O95O/article/details/133002995
https://learn.microsoft.com/zh-cn/dotnet/api/system.runtime.compilerservices.unsafeaccessorattribute?view=net-8.0

标签:Core,浅谈,反射,私有,Person,BindingFlags,类型,NET,Emit
From: https://www.cnblogs.com/lmy5215006/p/18545334

相关文章

  • .NetCore 6.0 Blazor WebAssembly 开发网页部署到IIS上
    一、安装、启用IIS服务使用ctrl+r打开运行输入optionalfeatures打开Windows功能管理窗口 开启Internet服务,点击确定 重启电脑开启IIS,查看IIS是否正常启动 打开默认IIS默认网站,查看是否正常开启 出现下图,即开启IIS服务成功二、安装.NETCoreSDK下载.NET......
  • 惊爆!72.1K star 的 Netdata:实时监控与可视化的超炫神器!
    在当今复杂的IT环境中,实时监控与可视化对于保障系统的稳定运行和性能优化至关重要。无论是服务器、应用程序,还是网络设备,及时获取性能数据能够帮助我们快速定位问题、优化资源配置。Netdata,作为一个开源的实时监控工具,正是为此而生。Netdata不仅是一个轻量级的监控与可视化平......
  • asp.net程序设计1946婚庆策划网站的设计与实现(源码)婚礼策划网站
    项目包含:源码、讲解视频、说明文档,部署录像请查看博主个人简介开发环境开发工具:VisualStudio2010或以上版本数据库:SQLServer2005或以上版本开发语言:c#操作系统:windows7或以上浏览器:GoogleChrome(推荐)、Edge、360浏览器系统用户分为:管理员、普通用户界面设计......
  • C++builder中的人工智能(28):FANN: Fast Artificial Neural Networks快速人工神经网络(ANN
    这篇文章全面介绍了快速人工神经网络(ANNs)的世界,探讨了它们在现代计算智能中的重要地位、核心特点、应用领域以及未来发展。快速人工神经网络库(FastArtificialNeuralNetworkLibrary,简称FANN)是一个免费的开源神经网络库,它使用C语言实现了多层人工神经网络,并支持全连接和稀疏......
  • 3、.Net UI库:CSharpSkin - 开源项目研究文章
    CSharpSkin(C#皮肤)是一个基于C#语言开发的UI框架,它允许开发者使用C#和.NET技术栈来创建跨平台的桌面应用程序。CSharpSkin框架通常用于实现具有自定义外观和感觉的应用程序界面,它提供了一套丰富的控件和组件,以及灵活的样式和布局系统。CSharpSkin的关键特性可能包括......
  • 【Azure App Service】部署在App Service上的.NET应用内存消耗不能超过2GB的情况分析
    问题描述.Net应用程序部署在AppServiceforWindows环境中,已经根据需求把Platform的位数由32bit修改位64bit。但是应用程序在运行一段时间后,一直抛出OutOfMemory异常。System.OutOfMemoryException: atSystem.GC.AllocateNewArray同时,查看AppService内存占用指标......
  • 深入理解 Kubernetes 中的 Service、Ingress 和 NginxIngress:如何配置多个域名访问 Ja
    个人名片......
  • asp.net使用Serilog按日志级别写入日志文件
    文章目录1、所需的Nuget包2、两种配置方式2-1代码形式(Program.cs)2-2配置文件形式(appsetting.json)3、实现效果如下1、所需的Nuget包我项目的版本是.NET8,其它版本安装适配版本即可2、两种配置方式2-1代码形式(Program.cs)在Program.cs文件中,添加如下代码//......
  • netCore物联网项目,分布式部署方案总结
    十年河东,十年河西,莫欺少年穷学无止境,精益求精部署环境:2台windowsServe服务器,2台linux服务器,阿里云CLB负载均衡服务器(2个),阿里云RabbitMQ,阿里云Ots表格存储(用于存物联网设备上报数据),Sqlserver服务器(存放业务主数据),Postgresql服务器(短期存放非业务主数据),阿里云Redis实例,对象存储OSS......
  • 开源框架NetCore
    推荐几个开箱即用的开源管理系统做项目   原文链接:https://blog.csdn.net/2401_83384536/article/details/140441595SCUIAdmin中后台前端解决方案SCUI是一个中后台前端解决方案,基于VUE3和elementPlus实现。使用最新的前端技术栈,提供各类实用的组件方便在业务开发时的......