深入了解.NET中的前期绑定与后期绑定
扫描二维码随身看资讯
使用手机 二维码应用 扫描右侧二维码,您可以
1. 在手机上细细品读~
2. 分享给您的微信好友或朋友圈~
反射,反射,程序员的快乐。
前期绑定与后期绑定
在.NET中,前期绑定(Early Binding)是指在编译时就确定了对象的类型和方法,而后期绑定(Late Binding)或动态绑定是在运行时确定对象的类型和方法。
前置知识:C#类型系统结构
C#作为C++++ ,在类型系统上沿用C++的类型系统
前期绑定
在代码能执行之前,将代码中依赖的assembly,module,class,method,field等
类型系统
的元素提前构建好。
前期绑定的优点是编译时类型检查,提高了类型安全性和性能。缺点是如果需要更换类型,需要重新编译代码。灵活性不够
比如一个简单的的控制台,就自动提前加载了各种需要的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中有哪些方法,哪些类,哪些属性等等信息
可以看到,各种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的源码
眼见为实,以Invoke为例
反射到底慢在哪?
-
动态解析
从上面可知道,反射作为后期绑定,在runtime中要根据metadata查询出信息,严重依赖字符串匹配,这本身就增加了额外的操作 -
动态调用
使用反射调用方法时,先要将参数打包成数组,再解包到线程栈上。又是额外操作。 -
无法在编译时优化
反射是动态的临时调用,JIT无法优化。只能根据代码一步一步执行。 -
额外的安全检查
反射会涉及到访问和修改只读字段等操作,运行时需要进行额外的安全性检查,这也会增加一定的开销 -
缓存易失效
反射如果参数发生变化,那么缓存的汇编就会失效。又需要重新查找与解析。
总之,千言万语汇成一句话。最好的反射就是不要用反射。除非你能保证 对性能要求不高/缓存高命中率
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开放了这么一个口子,利用外部访问直接操作内存。看它的命名
Unsafe
Accessor就能猜到意图了
3,2,1. 上汇编!!!
直接将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
60Seconds中文最新版下载 v1.0 安卓版
波比的游戏时间第3章 安卓手机版
众游传奇
植物大战僵尸杂交版 手机下载2025免费版
梅尔沃放置 官网版
黄金矿工 2025最新经典版
血腥画家恋爱中文版
群英三国GM版下载 v1.0.0 安卓版
生存要塞九游版下载 v1.0.73 安卓版
任性足球游戏手机版下载 v0.21.0 安卓版
使命召唤黑色行动僵尸手机版下载中文版 v1.0.11 安卓版
可口的咖啡无限金币钻石版本2024下载 v0.1.4 安卓版
宝可梦大集结全球服下载最新版本 v1.1.1.1 安卓版
鸣潮官方版
- .NET Core 泛型底层原理浅谈
- ASP.NET Core OData 9的发布,放弃 .NET Framework
- .NET 开源高性能 MQTT 类库
- 从0到1搭建权限管理系统系列三 .net8 JWT创建Token并使用
- 每周C#/.NET/.NET Core技术前沿周刊精选
- C#/.NET/.NET Core技术前沿周刊
- 使用Kiota工具在.NET环境下生成WebApi代理类
- 使用中台 Admin.Core 实现了一个Razor模板的通用代码生成器
- C#/.NET/.NET Core优秀项目和框架2024年6月简报
- 使用.NET中的System.IO.Compression进行文件和文件夹压缩的方法
- 开源.NET绘图库OxyPlot的跨平台应用
- 深度优化分布式缓存性能:微软推出.NET9中的HybridCache解决方案
- 1
新麻将连连看 消消乐
- 2
托卡3D版全部版中文版下载 v2.2.2 安卓版
- 3
天天酷跑3d单机游戏
- 4
茶香世家
- 5
植物大战僵尸杂交版 最新免费下载
- 6
人类游乐场 安卓免费版
- 7
猎鱼达人VIVO版本下载 v3.7.0.0 安卓版
- 8
植物大战僵尸幼儿园版 安卓官方版
- 9
希望之村2来生
- 10
米加小镇 免费无广告最新版
- 1
加查之花 正版
- 2
爪女孩 最新版
- 3
捕鱼大世界 无限金币版
- 4
企鹅岛 官方正版中文版
- 5
内蒙打大a真人版
- 6
跳跃之王手游
- 7
情商天花板 2024最新版
- 8
烦人的村民 手机版
- 9
球球英雄 手游
- 10
大富翁go 官网版