C#筆記 – 反射
反射
- 元數據表是程序集/模塊中各種成員定義的容器,(System.Reflection)反射則用來解析元數據表
- 實際上,System.Reflection中的類型為程序集/模塊中包含的元數據提供了一個對象模型
- 一般使用時機
- 需要理解類型的定義才能提供豐富功能的類庫
- 編輯器工具開發
- 從特定程序集加載特定類型以執行特定任務
- 晚期綁定:在運行時才確定要使用的類型和方法
- 需要理解類型的定義才能提供豐富功能的類庫
- 性能問題
- 反射嚴重依賴字符串
- 編譯時無法保證類型安全
- 反射的運行,實際上是在運行時,掃瞄程序集的元數據,不停地執行不區分大小寫的字符串搜索
- 調用方法的打包/解包
- 調用方法前,CLR會檢查實參具有正確的數據類型和有正確的安全權限
- 調用方法時,實參需要打包成數組;然後在內部,反射再將這些實參解包到線程棧上
- 替代方案
- 讓類型從編譯時已知的基類派生。運行時構造派生類的實例,將對它的引用放到基類的變量中,然後調用基類的虛方法
- 讓類型實現編譯時已知的接口。運行時構造類型的實例,將對它的引用放到接口類型的變量中,然後調用接口定義的方法
- 反射嚴重依賴字符串
- 用途
-
判斷程序集定義了哪些類型/成員
-
Assembly.ExportedTypes
-
System.Reflection.MemberInfo
- TypeInfo
- FieldInfo
- MethodBase
- ConstructorInfo
- MethodInfo
- PropertyInfo
- EventInfo
- 層次:
- 基於AppDomain -> 發現程序集
- 基於程序集 -> 發現模塊
- 基於程序集/模塊 -> 發現類型
- 基於類型 -> 發現
- 嵌套類型
- 字段
- 構造器
- 方法
- 屬性
- 事件
- 接口
- 基於各方法(構造/屬性訪問器/事件增刪/一般方法)-> 發現參數/返回值
- 基於任何一項都可發現定制特性
-
調用這些成員
-
成員類型 調用成員時需要使用的方法 FieldInfo GetValue/SetValue ConstructorInfo Invoke MethodInfo Invoke PropertyInfo GetValue/SetValue EventInfo AddEventHandler/RemoveEventHandler
-
-
運行時句柄
- RuntimeTypeHandle
- Type.GetTypeHandle -> RuntimeTypeHandle
- Type.GetTypeFromHandle -> Type
- RuntimeFieldHandle
- FieldInfo.FieldHandle -> RuntimeFieldHandle
- FieldInfo.GetFieldFromHandle -> FieldInfo
- RuntimeMethodHandle
- MethodInfo.MethodHandle -> RuntimeMethodHandle
- MethodInfo.GetMethodFromHandle -> MethodInfo
- 值類型,包含了一個IntPtr字段,用於引用AppDomain的Loader堆中的一個類型/字段/方法,目的是為了避免大量Type/MemberInfo派生對象的緩存
- RuntimeTypeHandle
-
-
得到類型對象的準確含義
-
Object.GetType
- 判斷指定對象的類型,返回該類型的Type對象引用
-
TestReflection tr1 = new TestReflection(); Console.WriteLine(tr1.GetType()); //CLR_Ch23.TestReflection
-
System.Type.GetType
-
必須傳遞一個字符串參數,指定類型的全名(包括命名空間)
-
不允許使用編譯器指定的基元類型
-
方法檢查調用程序集是否定義了指定名稱的類型,如果是,返回一個Type對象的引用
- 如果調用程序集中沒有定義,剛在MSCorLib.dll中查找
-
Type t = Type.GetType("CLR_Ch23.TestReflection"); Console.WriteLine(t.Name); //TestReflection Console.WriteLine(t.FullName); //CLR_Ch23.TestReflection
-
System.Type.ReflectionOnlyGetType
- 邏輯與GetType相似,但只以「僅反射」的方式加載,不能執行
-
-
System.TypeInfo
- 提供實例成員:DeclaredNestedTypes、GetDeclaredNestedType
- System.Type對象是輕量級的對象引用,而TypeInfo才代表真實類型定義
- Type<->TypeInfo
- 使用System.Reflection.IntrospectionExtensions.GetTypeInfo將Type轉換成TypeInfo
- TypeInfo.AsType將TypeInfo對象轉換成Type對象
- 獲取TypeInfo對象會強迫CLR確保已加載類型的定義程序集,從而對類型進行解析,代價高昂
- TypeInfo包括:
- 與類型關聯的標志,如:IsSealed, IsAbstract, IsClass, IsPublic, …
- 定義該類型的程序集的相關信息
- 類型基類的引用
- TypeInfo包括:
-
System.Reflection.Assembly
- 提供實例成員:GetType、DefinedTypes、ExportedTypes
-
typeof操作符
- 盡量用該操作符獲得type引用
- 一般用於將晚期綁定的類型信息與早期綁定的類型信息進行比較
-
if(tr1.GetType() == typeof(TestReflection)) { //... }
-
-
-
構造類型實例
-
Activator:提供了兩個靜態方法
-
CreateInstance
- 傳遞Type對象引用
-
Activator.CreateInstance(typeof(TestReflection)); -
返回新對象的引用
-
- 傳遞字符串對象引用
- 除了Type對象的名稱字符串,還需要傳遞定義了類型程序集的字符串實參
- 返回的是Runtime.Remoting.ObjectHandle對象
- 派生自MarshalByRefObject
- 因此允許將一個AppDomain中創建的對象傳至其他AppDomain
- 對象不會被實例化,直至調用對象的Unwrap方法
- 如果對象按引用封送,會創建代理類型和對象
- 如果對象按值封送,會反序列化對象的副本
-
ObjectHandle handle = Activator.CreateInstance(assemblyName, "TestReflection"); object tr3 = handle.Unwrap();
- 傳遞Type對象引用
-
CreateInstanceFrom
- 與CreateInstance行為相似,但必須通過字符串來指定類型和程序集,且內部使用LoadFrom
- 返回ObjectHandle,實例化需要手動調用Unwrap方法
-
-
AppDomain:提供了四個對象方法,行為與Activator類的方法相似
- CreateInstance
- CreateInstanceAndUnwrap
- CreateInstanceFrom
- CreateInstanceFromAndUnwrap
-
AppDomain d = AppDomain.CreateDomain("AD1"); d.CreateInstance("AssemblyName", "TypeName"); d.CreateInstanceAndUnwrap("AssemblyName", "TypeName"); d.CreateInstanceFrom("AssemblyName", "TypeName"); d.CreateInstanceFromAndUnwrap("AssemblyName", "TypeName");
-
數組實例創建:Array.CreateInstance
-
Array arr = Array.CreateInstance(typeof(TestReflection), 10);
-
-
委托實例創建:Delegate.CreateDelegate
-
var Instance = Activator.CreateInstance(typeof(TestReflection)); MethodInfo m = typeof(TestReflection).GetMethod("Test"); Action d = Delegate.CreateDelegate(typeof(Action), Instance, m) as Action; d();
-
-
泛型類型實例創建:Type.MakeGenericType
-
首先獲得對開放類型的引用
-
然後調用Type.MakeGenericType,並向其傳遞類型實參數組
-
最後調用構建類型實例的相關方法
-
Type openType = typeof(TestGeneric<,,>); Type closeType = openType.MakeGenericType(typeof(int), typeof(string), typeof(TestReflection)); Type[] arg = closeType.GetGenericArguments(); for (int i = 0; i < arg.Length; i++) { Console.WriteLine($"Arg{i}: {arg[i]}"); } -
依次輸出: Arg0: System.Int32 Arg1: System.String Arg2: CLR_Ch23.TestReflection -
然而,目前closeType必須通過GetTypeInfo才能得到具體的封閉類型,否則只能得到System.RuntimeType的結果
-
Console.WriteLine(closeType.GetType()); //System.RuntimeType Console.WriteLine(closeType.GetTypeInfo()); //CLR_Ch23.TestGeneric`3[System.Int32, System.String, CLR_Ch23.TestReflection]
-
-
-
-
參考書目
- 《CLR via C#》(第4版) Jeffrey Richter