元数据
元数据是用一系列表来存储的,生成一个程序集或模块时,编译器会创立一个类型定义表、一个字段定义表、一个办法定义表以及其它表。
反射
程序运行的时候解析这些元数据表以获取信息,该行为便是反射。反射容许在运行时发现并应用编译时还不理解的类型及其成员。
反射的性能
- 反射会造成编译时无奈保障类型安全性。反射须要重度依赖字符串,所以会丢失编译时的类型平安。例如执行 Type.GetType(“A”),在一个程序集中查找类型名为“A”的类型,但程序集理论蕴含的类型可能是“AA”,代码会通过编译,但运行时会出错。
- 反射速度慢,应用反射时,类型及其成员的名称在编译时未知,要应用字符串名称标识每个类型及其成员,以便在运行时发现它们。也就是说在扫描程序集的元数据时,反射要一直的执行字符串的搜寻,而字符串的搜寻执行的是不辨别大小写的比拟,这会进一步影响速度。
反射调用一个成员时也会对性能产生影响
反射调用办法时,首先必须将实参打包成一个数组,在外部反射必须将这些实参解包到线程栈上。此外在调用办法前,CLR 必须查看实参具备正确的数据类型,最初 CLR 还需确保调用者有正确的平安权限来拜访被调用的成员。
* 综上所述,最好防止应用反射技术来拜访字段或者调用办法。
获取 Type 对象的几种形式
- Type 的静态方法 GetType: 接管一个 string 参数,必须指定类型的全名 (包含命名空间),如果调用程序集没有定义指定的类型,就查找 MSCorLib.dll 定义的类型,如果还是没找到就返回 null
- Type 的静态方法 ReflectionOnlyGetType: 行为与 GetType 类似,只是类型会加载到一个“仅反射”上下文中,不能执行
构造类型的实例
- Activator 的静态方法 CreateInstance: 调用该办法能够传递一个 Type 对象援用,也能够传递标识了想要创立的类型的一个 String。其中间接获取一个类型对象的几个重载版本绝对简略,为类型的结构器传递一组实参,办法返回的是对新对象的一个援用。而应用字符串来指定所需类型的几个重载版本要略微简单一些,首先必须指定另一个字符串来标识定义了类型的那个程序集。其次这些版本返回的不是对新对象的援用,而是一个 System.Runtime.Remoting.ObjectHandle 对象。ObjectHandle 类型容许将一个 AppDomain 中创立的对象传至其它 AppDomain,期间不强制对象具体化,要具体化该对象能够调用 ObjectHandle 对象的 Unwrap 办法。在一个 AppDomain 中调用这个办法时,它会将定义了要具体化的类型的程序集加载到这个 AppDomain 中。如果对象按援用封送就创立代理类型和对象。如果按值封送,对象的正本会被反序列化。
- Activator 的静态方法 CreateInstanceFrom: 行为与 CreateInstance 办法类似,不同的是必须通过字符串来指定类型及其程序集。程序集要应用 Assembly 的 LoadFrom(而非 Load) 办法加载到调用的 AppDomain 中。因为没有接管 Type 参数的版本,所以返回的都是 ObjectHandle 对象的援用,必须调用 ObjectHandle 的 Unwrap 办法进行具体化
结构泛型类型的实例
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);
}
}
}
}
BindingFlags 枚举 (筛选返回的成员类型)
- Default:指定未定义任何绑定标记
- IgnoreCase:返回与指定字符串匹配的成员 (疏忽大小写)
- DeclaredOnly:只返回被反射的那个类型的成员,疏忽继承的成员
- Instance:返回实例成员
- Static:返回动态成员
- Public:返回公共成员
- NonPublic:返回非公共成员
- FlattenHierarchy:返回基类型定义的公共成员和受爱护动态成员
遍历反射对象模型图
基于一个 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 堆中的一个类型、字段或办法。
转化办法:
- Type 转 RuntimeTypeHandle: 调用 Type 的静态方法 GetTypeHandle
- RuntimeTypeHandle 转 Type: 调用 Type 的静态方法 GetTypeFromHandle
- FieldInfo 转 RuntimeFieldHandle: 查问 FieldInfo 实例只读字段 FieldHandle
- RuntimeFieldHandle 转 FieldInfo: 调用 FieldInfo 的静态方法 GetFieldFromHandle
- MethodInfo 转 RuntimeMethodHandle: 查问 MethodInfo 实例只读字段 MethodHandle
- RuntimeMethodHandle 转 MethodInfo: 调用 MethodInfo 的静态方法 GetMethodFromHandle
示例代码
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();}
后果