.NET Core 反射底層原理解析
簡介
反射,反射,程序員的快樂。
前期綁定與后期綁定
在.NET中,前期綁定(Early Binding)是指在編譯時就確定了對象的類型和方法,而后期綁定(Late Binding)或動態(tài)綁定是在運行時確定對象的類型和方法。
前置知識:C#類型系統(tǒng)結構
C#作為C++++ ,在類型系統(tǒng)上沿用C++的類型系統(tǒng)
前期綁定
在代碼能執(zhí)行之前,將代碼中依賴的assembly,module,class,method,field等類型系統(tǒng)的元素提前構建好。
前期綁定的優(yōu)點是編譯時類型檢查,提高了類型安全性和性能。缺點是如果需要更換類型,需要重新編譯代碼。靈活性不夠
比如一個簡單的的控制臺,就自動提前加載了各種需要的DLL文件。完成前期綁定。
后期綁定
后期綁定的優(yōu)點是可以在運行時更改類型,無需重新編譯代碼。缺點是在編譯時不進行類型檢查,可能導致運行時錯誤。
幾個常用的場景,比如dynamic ,多態(tài),System.Reflection等
舉個例子,使用Reflection下的“元數(shù)據(jù)查詢API”,動態(tài)加載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);//在托管堆中創(chuàng)建MT實例 MethodInfo addMethod = dataAccessType.GetMethod("Add");//構建MethodDesc addMethod.Invoke(dataAccess, new object[] { "hello world" });//調(diào)用方法
反射
反射的本質就是“操作元數(shù)據(jù)”
什么是元數(shù)據(jù)?
MetaData,本是上就是存儲在dll中的一個信息數(shù)據(jù)庫,記錄了這個assembled中有哪些方法,哪些類,哪些屬性等等信息
可以看到,各種Table組成的信息,是不是類似一個數(shù)據(jù)庫?
舉個例子:
執(zhí)行Type.GetType("int"),反射會在MetaData尋找"int"的類型。但在運行時會返回null.因為MetaData中只有"System.Int32"這個字符串。
反射如何查詢MetaData?
通過Reflection XXXInfo系列API 查詢所有細節(jié)
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); ......
反射如何構建類型系統(tǒng)
通過Reflection XXXBuilder系列API 構建一個全新的類型
AssemblyBuilder ab = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName("MyAssembly"), AssemblyBuilderAccess.RunAndCollect);//創(chuàng)建Assembly ModuleBuilder mob = ab.DefineDynamicModule("MyModule");//創(chuàng)建Module TypeBuilder tb = mob.DefineType("MyType", TypeAttributes.Public | TypeAttributes.Class);//創(chuàng)建Class MethodBuilder mb = tb.DefineMethod("SumMethod", MethodAttributes.Public | MethodAttributes.Static, typeof(int), new Type[] { typeof(int), typeof(int) });//創(chuàng)建MethodTable ILGenerator il = mb.GetILGenerator();//通過IL API 動態(tài)構建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 }));
反射底層調(diào)用
C#的類型系統(tǒng),與C++的類型系統(tǒng)是一一對應的。因此其底層必定是調(diào)用C++的方法。
示意圖如下,有興趣的小伙伴可以去參考coreclr的源碼
眼見為實,以Invoke為例
反射到底慢在哪?
- 動態(tài)解析
從上面可知道,反射作為后期綁定,在runtime中要根據(jù)metadata查詢出信息,嚴重依賴字符串匹配,這本身就增加了額外的操作 - 動態(tài)調(diào)用
使用反射調(diào)用方法時,先要將參數(shù)打包成數(shù)組,再解包到線程棧上。又是額外操作。 - 無法在編譯時優(yōu)化
反射是動態(tài)的臨時調(diào)用,JIT無法優(yōu)化。只能根據(jù)代碼一步一步執(zhí)行。 - 額外的安全檢查
反射會涉及到訪問和修改只讀字段等操作,運行時需要進行額外的安全性檢查,這也會增加一定的開銷 - 緩存易失效
反射如果參數(shù)發(fā)生變化,那么緩存的匯編就會失效。又需要重新查找與解析。
總之,千言萬語匯成一句話。最好的反射就是不要用反射。除非你能保證對性能要求不高/緩存高命中率
CLR的對反射的優(yōu)化
除了緩存反射的匯編,.NET 中提供了一系列新特性來盡可能的繞開“反射”
Emit
Emit 是 .NET 提供的一種動態(tài)生成和編譯代碼的技術。通過 Emit,我們可以動態(tài)生成一個新的方法,這個方法可以直接訪問私有成員,這對于一些特殊場景非常有用,比如動態(tài)代理、代碼生成器、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("未找到指定的私有字段"); } // 創(chuàng)建一個動態(tài)方法 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); // 返回(因為方法無返回值,這里只是結束方法執(zhí)行) ilGenerator.Emit(OpCodes.Ret); // 創(chuàng)建委托類型,其簽名與動態(tài)方法匹配 Action<Person, int> setAgeAction = (Action<Person, int>)dynamicMethod.CreateDelegate(typeof(Action<Person, int>)); // 通過委托調(diào)用動態(tài)生成的方法來修改私有字段的值 setAgeAction(person, 100); }
切構建代碼又臭又長。
Expression
Expression 是 .NET 提供的一種表達式樹的技術。通過 Expression,我們可以創(chuàng)建一個表達式樹,然后編譯這個表達式樹,生成一個可以訪問私有成員的方法
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("未找到指定的私有字段"); } // 創(chuàng)建參數(shù)表達式,對應傳入的Person對象實例 ParameterExpression instanceParam = Expression.Parameter(personType, "instance"); // 創(chuàng)建參數(shù)表達式,對應傳入的新值 ParameterExpression newValueParam = Expression.Parameter(typeof(int), "newValue"); // 創(chuàng)建一個賦值表達式,將新值賦給私有字段 BinaryExpression assignExpression = Expression.Assign(Expression.Field(instanceParam, ageField), newValueParam); // 創(chuàng)建一個包含賦值表達式的表達式塊,這里因為只有一個賦值操作,所以塊里就這一個表達式 BlockExpression blockExpression = Expression.Block(assignExpression); // 創(chuàng)建一個可執(zhí)行的委托,其類型與表達式塊的邏輯匹配 Action<Person, int> setAgeAction = Expression.Lambda<Action<Person, int>>(blockExpression, instanceParam, newValueParam).Compile(); // 通過委托調(diào)用表達式樹生成的邏輯來修改私有字段的值 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語言的定義。它定義了什么是私有類型,它的行為是什么。
但對于程序本身來說,代碼和數(shù)據(jù)都只是一段內(nèi)存,實際上你的指針想訪問哪就訪問哪。哪管你什么私有類型。換一個指向地址不就得了。因此CLR開放了這么一個口子,利用外部訪問直接操作內(nèi)存??此拿鸘nsafeAccessor就能猜到意圖了
3,2,1. 上匯編?。。?/p>
直接將rax寄存器偏移量+8,直接返回int(占用4字節(jié),偏移量8)類型的_age。 沒有Emit,Expression的彎彎繞繞,絲毫不拖泥帶水。
.NET 9中的改進
支持泛型,更優(yōu)雅。
https://learn.microsoft.com/zh-cn/dotnet/core/compatibility/core-libraries/9.0/unsafeaccessor-generics
參考資料
到此這篇關于.NET Core 反射底層原理淺談的文章就介紹到這了,更多相關.NET Core 反射底層原理內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
C#開發(fā)微信 二維碼鼠標滑動 圖像顯示隱藏效果(推薦)
客戶端微信在二維碼狀態(tài)下,鼠標滑過,會有一張手機的圖片滑動滑出,從隱藏到顯示,從顯示到隱藏。效果非常棒,本文思路介紹明確,感興趣的朋友一起看看吧2016-11-11asp.net?core集成ElasticSearch實現(xiàn)全文檢索功能
索引是Elasticsearch中用于存儲文檔的容器,你可以使用Elasticsearch的REST?API、官方客戶端庫(如NEST)或Kibana等工具來創(chuàng)建和管理索引,本文給大家介紹asp.net?core集成ElasticSearch實現(xiàn)全文檢索功能,感興趣的朋友一起看看吧2024-08-08.NET?Core使用Worker?Service創(chuàng)建服務
這篇文章介紹了.NET?Core使用Worker?Service創(chuàng)建服務的方法,文中通過示例代碼介紹的非常詳細。對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-02-02