反射
元數據表是程序集/模塊中各種成員定義的容器,(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派生對象的緩存
得到類型對象的準確含義
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, …
定義該類型的程序集的相關信息
類型基類的引用
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();
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