版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
1、摘 要反射,一个很有用且有意思的特性。当动态创建某个类型的实例或是调用方法或是访问对象成员时通常会用到它,它是基于程序集及元数据而工作的,所以这一章我们来讨论一下程序集、反射如何工作、如何动态创建类型及对象等相关知识,甚至可以动态创建程序集。第一节 应用程序域与程序集通过本系列的前面章节,我们已经知道,Windows为每个进程分配独立的内存空间地址,各个进程之间不能直接相互访问。Windows对.NET的支持是以宿主和COM的形式实现的,基于.NET平台语言实现的代码文件使用Windows PE的文件格式,CLR其实就是COM,相当于一个虚拟机(当然这个虚拟机可以部署到任意支持它的系统环境中)
2、,在安装.NET Framework时,CLR的组件与其他COM一样在Windows系统中享有同等的待遇,当CLR启动初始化时会创建一个应用程序域,应用程序域是一组程序集的逻辑容器,它会随着进程的终止而被卸载销毁,CLR把程序代码所需要的程序集加载到当前(或指定的)应用程序域内。CLR可以以其初始化时创建的应用程序域为基础再创建其他的新应用程序域,两个应用程序域中的代码不能直接访问,当然可以通过“中介”进行数据传送。新的程序域创建完后CLR完全可以卸载它,以同步方式调用AppDomain.Unload方法即可,调用此方法后,CLR会挂起当前进程中的所有线程,接着查找并中止运行在即将卸载的程序域
3、内的线程,然后进行垃圾回收,最后主线程恢复运行。任何Windows程序都可以寄宿CLR,一台机上可以安装多个版本的CLR。Windows在启动一个托管的程序时会先启动MSCorEE.dll中的一个方法,该方法在内部根据一个托管的可执行文件信息来加载相应版本的CLR,CLR初始完成之后,将程序集加载到应用程序域,最后CLR检查程序集的CLR头信息找到Main方法并执行它。 第二节 加载程序集程序集是所有类型的集合,它还有一个重要的东西就是元数据。JIT就是利用程序集的TypeRef和AssemblyRef等元数据来确定所引用的程序集及类型,这些元数据包括名称、版本、语言文化和公钥标记等
4、,JIT就是根据这些信息来加载一个程序集到应用程序域中。如果要自己加载一个程序集,可以调用类型Assembly的LoadXXX系列方法。(1) Load重载系列该方法会按照一定的顺序查找指定目录中的程序集:先去GAC中查找(如果是一个强命名程序集),如果找不到, 则去应用程序的基目录、子目录查找。如果都没找到,则抛出异常。如下代码加载程序集MyAssemblyB: string assemblyName = "MyAssemblyB, Version=, Culture=neutral, PublicKeyToken=null" Assembly assemb
5、ly = Assembly.Load(assemblyName);(2) LoadFrom重载系列加载指定程序集名称或路径的程序集,其在内部调用Load方法,并且还可以指定一个网络路径,如果指定网络路径,则先下载该程序集,再将其加载到程序域,如下代码:Assembly.LoadFrom("(3) LoadFile重载系列从任意路径加载一个程序集,并且可以从不同路径加载相同名称的程序集。在一个项目中,可能程序集之间都有依赖关系,也可以将一个程序集作为资源数据嵌入到一个程序集中,在需要时再加载该程序集,这时通过注册ResolveAssembly事件来加载这个程序集。如下; AppDoma
6、in.CurrentDomain.AssemblyResolve += (sender, arg) => byte buffer = null; using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("ConsoleApp.MyAssemblyA.dll") buffer = new bytestream.Length; stream.Read(buffer, 0, buffer.Length); return Assembly.Load(buffer); ;以
7、上代码要求必须先将MyAssemblyA.dll文件以资源形式嵌入到ConsoleApp项目中。这样在运行ConsoleApp程序时,如果使用了MyAssemblyA中的类型且未找到MyAssemblyA.dll文件,则会进入上面的事件方法来加载程序集MyAssemblyA。如果只是想了解一个程序集的元数据分析其类型而不调用类型的成员,为了提高性能,可以调用这些方法:Assembly.ReflectionOnlyLoadFrom(String assemblyFile)Assembly.ReflectionOnlyLoad(byte rawAssembly)Assembly.Reflectio
8、nOnlyLoad(String assemblyName)如果试图调用上面这三个方法加载的程序集中类型的代码,则CLR会抛出异常。 第三节 反射我们知道,在程序集(或模块)内有一个很重要的数据就是元数据,它们描述了类型定义表,字段定义表,方法表等,也就是说所有的类型及成员定义项都会在这里被清楚详细地记录下来。很明显, 如果我们拿到了这些“描述信息”,当然就相当于已经明确知道了一个类型及其成员,进而就可以“构造”这个类型,通过反射就可以达到这样的目的。另人高兴的是我们不用分析那些元数据就可以方便地得到程序集内的类型成员,.NET Framework提供了一些与此相关的类定义在命名空间
9、System.Reflection下。反射提供了封装程序集、模块和类型的对象(Type 类型)。反射机制运行在程序运行时动态发现类型及其成员。(1)查找程序集内所定义的类型在将某一程序集加载到应用程序域后,可以通过Assembly的GetExportedTypes方法来获取该程序集所有的公开类型,如下代码: private void GetTypes() string assemblyName = "MyAssemblyB, Version=, Culture=neutral, PublicKeyToken=null" Assembly assembly =
10、Assembly.Load(assemblyName); Type types = assembly.GetExportedTypes(); foreach (Type t in types) Console.WriteLine(t.Name); (2)查找类型成员在命名空间System.Reflection中有一个抽象类型MemberInfo,它封装了与类型成员相关的通用属性,每一个类型成员都有一个对应的从MemberInfo派生而来的类型,并且内置了一些特殊的属性特征,如FieldInfo、MethodBase(ContructorInfo、MethodInfo)、PropertyInfo
11、和EventInfo。可以通过调用类型Type对象的GetMembers方法获取该类型的所有成员或相应成员,如下代码(对上面的GetTypes方法的修改)获取全部成员列表: Type types = assembly.GetExportedTypes(); foreach (Type t in types) Console.WriteLine(t.Name); MemberInfo members = t.GetMembers(); Type有一组GetXXX方法是获取对象成员的,以下列出部分方法:GetConstructor/GetConstructors /获取构造函数GetEvent/G
12、etEvents /获取事件GetField/GetFields /获取字段GetMethod/GetMethods /获取方法GetProperty/GetProperties /获取属性并且每个方法都可以接收一个枚举类型BindingFlags的参数指定控制绑定和由反射执行的成员和类型搜索方法的标志。有关BindingFlags 枚举可参考MSDN文档如下代码获取AudiCar类型的Owner属性和Run()方法: private void GetTypeMethod() string assemblyName = "MyAssemblyB, Version=, C
13、ulture=neutral, PublicKeyToken=null" Assembly assembly = Assembly.Load(assemblyName); Type t = assembly.GetType("MyAssemblyB.AudiCar"); MethodInfo method = t.GetMethod("Run"); PropertyInfo pro = t.GetProperty("Owner"); (3)构造类型实例在拿到类型及成员信息之后,我们就可以构造类型的实例对象了。FCL提供了几个
14、方法来构造一个类型的实例对象,有关这些方法详细内容,可参考MSDN文档:Activator.CreateInstance() /重载系列Activator.CreateInstanceFrom() /重载系列AppDomain.CurrentDomain.CreateInstance() /重载系列AppDomain.CurrentDomain.CreateInstanceFrom() /重载系列如下构造AudiCar类型的实例: private void TestCreateInstance() string assemblyName = "MyAssemblyB, Version
15、=, Culture=neutral, PublicKeyToken=null" Assembly assembly = Assembly.Load(assemblyName); Type t = assembly.GetType("MyAssemblyB.AudiCar"); var obj = Activator.CreateInstance(t); Debug.Assert(obj != null); 看一下调试:另外,还可以调用类型的构造函数创建实例对象,如下:obj = t.InvokeMember("AudiCar",
16、 BindingFlags.CreateInstance, null, null, null); 第四节 通过反射访问对象成员如果仅仅得到类型的对象,好像意义并不大,我们更多的是要操作对象,比如访问属性,调用方法等,这一节我们来看一下如何访问成员。类型Type提供了一个访问目标类型成员的非常靠谱的方法InvokeMember,调用此方法时,它会在类型成员中找到目标成员(这通常指定成员名称,也可以指定搜索筛选条件BindingFlags,如果调用的目标成员是方法,还可以给方法传递参数。),如果找到则调用目标方法,并返回目标访问返回的结果,如果未找到,则抛出异常,如果是在目标方法内部有异
17、常,则InvokeMember会先捕获该异常,包装后再抛出新的异常TargetInvocationException。以下是InvokeMember方法的原型:public object InvokeMember(string name, BindingFlags invokeAttr, Binder binder, object target, object args);public object InvokeMember(string name, BindingFlags invokeAttr, Binder binder, object target, object args, Cult
18、ureInfo culture);public abstract object InvokeMember(string name, BindingFlags invokeAttr, Binder binder, object target, object args, ParameterModifier modifiers, CultureInfo culture, string namedParameters);name 目标方法名称invokeAttr 查找成员筛选器binder 规定了匹配成员和实参的规则target 要调用其成员的对象args 传递给目标方法的参数在上一节的最后我们展示了
19、如何调用类型的构造函数来实例化一个对象,下面的代码演示了如何调用对象的方法,其中方法Turn接收一个Direction类型的参数: string assemblyName = "MyAssemblyB, Version=, Culture=neutral, PublicKeyToken=null" Assembly assembly = Assembly.Load(assemblyName); Type t = assembly.GetType("MyAssemblyB.AudiCar"); var obj = Activator.Crea
20、teInstance(t); t.InvokeMember("Turn", BindingFlags.InvokeMethod, null, obj, new object Direction.East );另外,调用目标对象的方法,还可以以MethodInfo的方式进行,如下: Type t = assembly.GetType("MyAssemblyB.AudiCar"); var obj = Activator.CreateInstance(t); MethodInfo method = t.GetMethod("Turn");
21、 method.Invoke(obj, new object Direction.Weast );以下是对属性的读写操作: Type t = assembly.GetType("MyAssemblyB.AudiCar"); var obj = Activator.CreateInstance(t); /为属性Owner赋值 obj.GetType().GetProperty("Owner").SetValue(obj, "张三", null); /读取属性Owner的值 string name = (string)obj.GetTyp
22、e().GetProperty("Owner").GetValue(obj, null);对于其他成员(如字段等)的访问,可参考MSDN文档。反射对泛型的支持以上的演示都是针对普通类型,其实反射也提供了对泛型的支持,这里只简单演示一下反射对泛型的简单操作。比如我们有如下一个泛型类型定义:namespace MyAssemblyB public class MyGeneric<T> public string GetName<T>(T name) return "Generic Name:" + name.ToString(); 这
23、个类型很简单,类型MyGeneric内有一个方法,该方法返回带有附加信息” Generic Name:”的名称。先来看一下如何获取指定参数类型为string的泛型类: private void TestGenericType() string assemblyName = "MyAssemblyB, Version=, Culture=neutral, PublicKeyToken=null" Assembly assembly = Assembly.Load(assemblyName); Type types = assembly.GetExportedTy
24、pes(); foreach (Type t in types) /检测是否泛型(在程序集MyAssemblyB中只定义了一个泛型类型 MyGeneric<T>) if (t.IsGenericType) /为泛型类型参数指定System.String类型,并创建实例 object obj = Activator.CreateInstance(t.MakeGenericType(new Type typeof(System.String) ); /生成泛型方法 MethodInfo m = obj.GetType().GetMethod("GetName").M
25、akeGenericMethod(new Type typeof(System.String) ); /调用泛型方法 var value = m.Invoke(obj, new object "a" ); Console.WriteLine(value); 调试起来,看一下最终的value值:反射泛型的时候,要先确定目标类型是泛型,在创建泛型类型实例前,必须调用MakeGenericType方法构造一个真正的泛型,该方法接收一个要指定泛型类型参数的类型数组,同样调用泛型方法前要调用方法MakeGenericMethod构造相应的泛型方法,此方法也接收一个指定泛型类型的类型数
26、组。 第五节 动态创建类型前面几节所描述的都是基于已经存在程序集的情况下进行反射,.NET Framework还提供了在内存中动态创建类型的强大功能。我们知道程序集包括模块,模块包括类型,类型包括成员,在动态创建类型的时候也是要遵循这个顺序。动态创建类型是基于元数据的实现方式来实现的,这一部分被定义在命名空间System.Reflection.Emit内,有一系列的XXXBuilder构造器来创建相应的类型对象。我们来看一要动态创建类型,有哪些步骤(这里只是简单演示):(1) 程序集是老窝,所以要先创建一个程序集:AssemblyBuilder aBuilder = AppDomai
27、n.CurrentDomain.DefineDynamicAssembly(new AssemblyName("TempDynamicAssembly"), AssemblyBuilderAccess.Run);(2) 有了程序集,接下来是模块ModuleBuilder mBuilder = aBuilder.DefineDynamicModule("NotifyPropertyChangedObject");(3) 接下来就是创建类型了:this.tBuilder = mBuilder.DefineType(typeFullName, TypeAttr
28、ibutes.Public | TypeAttributes.BeforeFieldInit);(4) 现在可以创建类型的成员了,为类型创建一个属性Name。我们知道属性包含字段和对字段的两个访问器,所以应该先创建字段,然后再创建两个访问器方法,这一段是按照IL码的先后顺序来的,如下: FieldBuilder fieldBuilder = this.tBuilder.DefineField(string.Format("0Field", propertyName), propertyType, FieldAttributes.Private); PropertyBuild
29、er propertyBuilder = tBuilder.DefineProperty(propertyName, PropertyAttributes.HasDefault, propertyType, null); MethodAttributes getSetAttr = MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig; MethodBuilder getAccessor = tBuilder.DefineMethod(string.Format("get_
30、0", propertyName), getSetAttr, propertyType, Type.EmptyTypes); ILGenerator getIL = getAccessor.GetILGenerator(); getIL.Emit(OpCodes.Ldarg_0); getIL.Emit(OpCodes.Ldfld, fieldBuilder); getIL.Emit(OpCodes.Ret); propertyBuilder.SetGetMethod(getAccessor); MethodBuilder setAccessor = tBuilder.DefineM
31、ethod(string.Format("set_0", propertyName), getSetAttr, null, new Type propertyType ); setAccessor.DefineParameter(1, ParameterAttributes.None, "value"); ILGenerator setIL = setAccessor.GetILGenerator(); setIL.Emit(OpCodes.Nop); setIL.Emit(OpCodes.Ldarg_0); setIL.Emit(OpCodes.Lda
32、rg_1); setIL.Emit(OpCodes.Stfld, fieldBuilder); setIL.Emit(OpCodes.Ldarg_0); setIL.Emit(OpCodes.Ldstr, propertyName); setIL.Emit(OpCodes.Call, this.mBuilder); setIL.Emit(OpCodes.Nop); setIL.Emit(OpCodes.Ret); propertyBuilder.SetSetMethod(setAccessor);注意,这里面有对事件的操作,可以忽略。(5) 最后调用类型构造器的CreateType()方法就可
33、以创建该类型了:tBuilder.CreateType();该方法返回一个Type类型。类型创建完成后,我们就可以使用上一节讲的反射相关知识对该类型进行操作了,这里当然是一个简单的类型,如果想创建复杂的类型,比如有方法,事件等成员,那可以发挥你的汇编能力来慢慢折腾吧,也可以体味一下当时汇编程序员们的苦逼!托管下的汇编编码已经很简化了,围绕Emit方法折腾死!如果想研究IL,可以用IL DASM打开托管程序集,慢慢欣赏吧。在我们的日常开发中,有时用了动态类型还是很方便的,比如当你要创建一个DataGrid的数据源DataTable,但多少列不确定,列的数据类型不确定,列名也不确定的情况下,这时根
34、据要求创建一个动态类型,继而再创建一个该类型的集合就很方便使用了。我封装了一个动态创建类型的类,在本文的结尾提供下载,喜欢的可以拿去。这里所描述的是动态地在内存创建一个类,关于动态类型dynamic和var,这里就不再瞎掰了,感兴趣的可以去查找相关资料。 第六节 应用反射时要注意的几点反射为我们开发提供了非常便利的编程实践,但使用它也有几点需要注意。既然是反射,我们在编码时对类型是未知的,如果是已知,就没必要再用反射了, 除非是要做类似分析类型元数据的工具,而我们一般使用反射是要操作其属性字段、调用其方法等,目的是用而不是分析。在编译使用了反射的代码过程中,反射的目标类型是不安全的,
35、很有可能在调用反射出来的类对象时出错,这一点要注意。反射是基于元数据实现的,所以在使用反射过程中,代码会搜索程序集的元数据,这些元数据是基于字符串的,并且无法预编译,所以这一系列的操作对性能有严重影响。另外,由于我们对目标类型未知,在向方法传递参数时通常是以object数组传递,CLR会逐个检查参数的数据类型,无论是传入还是返回,都有可能进行大量的类型转换,这也损伤了性能。所以对于反射的应用,应该注意。当然,像一些ORM等框架是以牺牲性能来换取方便的开发体验就另当别说了。 第七节 一个简单的插件项目最后我们来演示一个简单的支持插件的小项目也可以弥补上面几节中丢失的代码块。总共有三个项目,模拟对车进行跑和转弯测试,如图:ConsoleApp项目是测试程序MyAssemblyA 是整个插件系列的接口契约,定义了汽车接口的跑动作Run和转变动作Turn,我们约定所有的其他插件车必须符合这个契约,即实现这个接口。接口如下:namespace MyAssemblyA public interface ICar void Run(); void Turn(Direction direction); namespace MyAssemblyA public enum Direction East, Weast, South, North MyAssemblyB 插件程序,任何
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 湿气测试方法结果解读手册
- 在线安全教育培训管理制度
- 四季养生膳食调理操作规范
- 烤烟移栽后田间管理操作规程
- 糖尿病一日三餐配餐服务指南
- 草莓脱毒苗繁育操作技术规范
- 中医足疗技师操作规范
- 胃病康复期饮食禁忌指南标准
- 广东省珠海市2026年第二学期九年级第一次模拟考试数学试卷附答案
- 厂界噪声监测控制规范流程
- 西藏自治区日喀则市2026届高三第二次模拟考试语文试卷含解析
- 辽宁省能源集团招聘笔试题库2026
- 管道拆除安全措施方案
- 成人2型糖尿病口服降糖药联合治疗专家共识(2025版)课件
- 英语北京市昌平区2026年高三年级第一次统一练习(昌平高三一模)(4.7-4.10)
- 2026成都市八年级语文下册部编版期末考试卷含答案
- 便利店工作制度详细流程
- 2025秋季《中华民族共同体概论》期末综合考试-国开(XJ)-参考资料
- 高速公路服务区建设项目可行性研究报告
- LY/T 2015-2012大熊猫饲养管理技术规程
- 文史资料选辑合订本(46卷本第1辑至第136辑)
评论
0/150
提交评论