前期綁定與后期綁定 在.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);
Type dataAccessType = assembly.GetType("xxxxx" );
object dataAccess = Activator.CreateInstance(dataAccessType);
MethodInfo addMethod = dataAccessType.GetMethod("Add" );
addMethod.Invoke(dataAccess, new object [] { "hello world" });
反射 反射的本質就是“操作元數據”
什么是元數據? MetaData,本是上就是存儲在dll中的一個信息數據庫,記錄了這個assembled中有哪些方法,哪些類,哪些屬性等等信息 可以看到,各種Table組成的信息,是不是類似一個數據庫?
舉個例子: 執行Type.GetType("int"),反射會在MetaData尋找"int"的類型。但在運行時會返回null.因為MetaData中只有"System.Int32"這個字符串。
通過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);
ModuleBuilder mob = ab.DefineDynamicModule("MyModule" );
TypeBuilder tb = mob.DefineType("MyType" , TypeAttributes.Public | TypeAttributes.Class);
MethodBuilder mb = tb.DefineMethod("SumMethod" , MethodAttributes.Public | MethodAttributes.Static, typeof(int ), new Type [] { typeof(int ), typeof(int ) });
ILGenerator il = mb.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Add);
il.Emit(OpCodes.Ret);
Type type = tb.CreateType();
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 )
{
Type personType = typeof (Person);
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);
ILGenerator ilGenerator = dynamicMethod.GetILGenerator();
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)
{
Type personType = typeof(Person);
FieldInfo ageField = personType.GetField("_age" , BindingFlags.Instance | BindingFlags.NonPublic);
if (ageField == null )
{
throw new ArgumentException ("未找到指定的私有字段" );
}
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
轉自https://www.cnblogs.com/lmy5215006/p/18545334
該文章在 2024/11/16 8:18:06 編輯過