元数据是用一系列表来存储的,生成一个程序集或模块时,编译器会创建一个类型定义表、一个字段定义表、一个方法定义表以及其它表。
程序运行的时候解析这些元数据表以获取信息,该行为便是反射。反射允许在运行时发现并使用编译时还不了解的类型及其成员。
反射调用方法时,首先必须将实参打包成一个数组,在内部反射必须将这些实参解包到线程栈上。此外在调用方法前,CLR必须检查实参具有正确的数据类型,最后CLR还需确保调用者有正确的安全权限来访问被调用的成员。
*综上所述,最好避免使用反射技术来访问字段或者调用方法。
static void Main() { //获取泛型类型的类型对象的一个引用 Type temp = typeof(List<>); //使用int类型封闭泛型类型 Type closedType = temp.MakeGenericType(typeof(int)); //构造封闭类型的实例 object o = Activator.CreateInstance(closedType); Console.WriteLine(o.GetType()); }
运行结果
字段、构造器、方法、属性、事件和嵌套类型都可以被定义为类型的成员。FCL包含一个System.Reflection.MemberInfo的类型,封装了一组所有类型都通用的属性。层次结构图如下:
static void Main() { Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); //遍历AppDomain中加载的所有程序集 foreach (var item in assemblies) { Console.WriteLine("Assembly: {0}", item); //查找程序集中的类型 foreach (var t in item.GetExportedTypes()) { Console.WriteLine("Type: {0}", t); //发现类型成员 const BindingFlags bf = BindingFlags.DeclaredOnly | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static; foreach (var mi in t.GetMembers(bf)) { var typeName = string.Empty; if (mi is Type) typeName = "Type"; else if (mi is FieldInfo) typeName = "FieldInfo"; else if (mi is MethodInfo) typeName = "MethodInfo"; else if (mi is ConstructorInfo) typeName = "ConstructorInfo"; else if (mi is PropertyInfo) typeName = "PropertyInfo"; else if (mi is EventInfo) typeName = "EventInfo"; Console.WriteLine("mi typeName: {0}", typeName); } } } }
遍历反射对象模型图
基于一个AppDomain可发现加载到其中的所有程序集。基于一个程序集可发现构成它的所有模块。基于一个程序集或模块可发现它定义的所有类型。基于一个类型可发现它的嵌套类型、字段、构造器、方法、属性和事件
interface ITest1 : IDisposable { void Method1(); void Method2(); } interface ITest2 { void Method1(); } class MyClass : ITest1, ITest2, IDisposable { //ITest1 void ITest1.Method1() { } public void Method2() { } //ITest2 void ITest2.Method1() { } //IDisposable public void Dispose() { } //非接口方法 public void Method1() { } } class Program { static void Main() { //查找MyClass实现的接口 Type t = typeof(MyClass); Type[] interfaces = t.FindInterfaces(TypeFilter, typeof(Program).Assembly); //显示每个接口的信息 foreach (var item in interfaces) { Console.WriteLine("接口: {0}", item); //获取接口映射 InterfaceMapping map = t.GetInterfaceMap(item); for (int i = 0; i < map.InterfaceMethods.Length; i++) { Console.WriteLine("方法 {0} 在 {1} 中实现", map.InterfaceMethods[i], map.TargetMethods[i]); } } Console.ReadKey(); } private static bool TypeFilter(Type t, object filterCriteria) { //如果接口是由filterCriteria标识的程序集中定义的就返回true return t.Assembly == (Assembly)filterCriteria; } }
结果
Type类提供了一个InvokeMember方法,可以通过这个方法调用一个类型的成员。在调用这个方法时,会在类型的成员中搜索一个匹配的成员,如果没有找到则会抛出一个异常。反之则会调用成员,该成员返回什么InvokeMember方法也会返回一个同样的信息数据。
InvokeMember使用的BindingFlags
使用InvokeMember方法的弊端在于每次调用该方法,都必须先绑定到一个特定的成员然后才能调用,这大大地增加了耗时。可以直接调用Type的某个方法来绑定成员(GetFields,GetMethods等),可以返回对一个对象的引用,通过对象引用直接访问特定成员。
示例代码
class SomeType { private int m_SomeValue; public int SomeValue { get { return this.m_SomeValue; } set { this.m_SomeValue = value; } } public string SomeMethod() { Console.WriteLine("Run SomeMethod"); return "SomeMethod Rusult"; } } static void Main() { var bf = BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; Type t = typeof(SomeType); //构造Type实例 object o = t.InvokeMember(null, bf | BindingFlags.CreateInstance, null, null, null); Console.WriteLine("o type : {0}", o.GetType().ToString()); //读写字段 t.InvokeMember("m_SomeValue", bf | BindingFlags.SetField, null, o, new object[] { 1 }); int v = (int)t.InvokeMember("m_SomeValue", bf | BindingFlags.GetField, null, o, null); Console.WriteLine("m_SomeValue = {0}", v); //调用一个方法 string methodRes = (string)t.InvokeMember("SomeMethod", bf | BindingFlags.InvokeMethod, null, o, null); Console.WriteLine("methodRes = {0}", methodRes); Console.WriteLine("\n---------分割线---------\n"); //构造实例 ConstructorInfo ctor = t.GetConstructor(new Type[] { }); o = ctor.Invoke(null); //读写字段 FieldInfo fi = o.GetType().GetField("m_SomeValue", bf); fi.SetValue(o, 2); Console.WriteLine("m_SomeValue = {0}", fi.GetValue(o)); //调用方法 MethodInfo mi = o.GetType().GetMethod("SomeMethod", bf); methodRes = (string)mi.Invoke(o, null); Console.WriteLine("methodRes = {0}", methodRes); Console.ReadKey(); }
结果
由于Type和MemberInfo的派生对象需要大量内存,因此如果应用程序容纳了太多这样的对象,但只是偶尔调用一下,内存消耗将急剧增长,对性能产生负面影响。
解决方法:如果需要大量缓存Type和MemberInfo的派生对象,我们可以使用允许时句柄来替代对象,从而减小工作集。FCL定义了三个运行时句柄类型,分别是:RuntimeTypeHandle,RuntimeFieldHandle,RuntimeMethodHandle,三个类型均属于值类型,只包含一个IntPtr字段,这个字段是一个句柄,引用了AppDomain的Loader堆中的一个类型、字段或方法。
转化方法:
示例代码
private static void Show(string s) { Console.WriteLine("堆大小 : {0,12:##,###,###} - {1}", GC.GetTotalMemory(true), s); } static void Main() { var bf = BindingFlags.FlattenHierarchy | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic; Show("任何反射操作之前的堆的大小"); //为MSCorlid.dll中所有方法构建MethodInfo对象缓存 List<MethodBase> methodBases = new List<MethodBase>(); foreach (var item in typeof(Object).Assembly.GetExportedTypes()) { if (item.IsGenericTypeDefinition) continue; MethodBase[] mb = item.GetMethods(bf); methodBases.AddRange(mb); } Console.WriteLine("方法个数 : {0}", methodBases.Count); Show("绑定所有方法后堆的大小"); //为所有MethodInfo对象构建RuntimeMethodHabdle缓存 List<RuntimeMethodHandle> runtimeMethodHandles = methodBases.ConvertAll<RuntimeMethodHandle>(mb => mb.MethodHandle); Show("构建RuntimeMethodHandle后堆的大小"); //阻止缓存被过早垃圾回收 GC.KeepAlive(methodBases); //垃圾回收 methodBases = null; Show("垃圾回收methodBases后堆的大小"); Console.ReadKey(); }
结果