C#基础系列-反射
一、定义
Reflection翻译成反射,在实际生活中比如地质勘探中如何了解地球内部构造情况(地壳、地幔和地核),因为没办法通过设备钻入地球深入勘查,就想出对地球发送“地震波”的方式,“横向波”与“纵向波”穿透液体和固体返回情况构建地球内部的结构。反射类比于此,这是一种对象的外部获取对象内部的构造,并且使用获取的信息来管理对象内部。.
反射是提供描述程序集(Assembly)、模块和类型的对象(Type类型)。 可以使用反射动态地创建类型的实例,将类型绑定到现有对象,或从现有对象中获取类型,然后调用其方法或访问器字段和属性, 如果代码中使用了特性,还可以利用反射来访问它们。.NET反射可以在运行时(动态)获取程序或程序集、程序集的类、接口、委托、类中的成员。主要使用方式通过程序集(dll)名称动态加载程序集;通过程序集中的类反射动态创建类实例对象,操作实例对象;通过已经创建的对象反射获取对象中的方法、属性、参数进行执行方法,设置属性值等;获取类上描述的特性(Attribute)。
二、原理
在面试中突然被问道反射的原理,按照理解反射就是在Reflection命名空间和对象的Type对象实例中获取类的方法、属性、特性等成员信息,但是又被问道为什么可以获取这些成员信息?就是反射机通过如下命名空间的方法为什么可以获取?让其可以使用反射来获取程序集、程序集的类、创建对象、执行方法、获取属性和特性信息。
命名空间 | 描述 |
System.Reflection.Assembly | 程序集 |
System.Reflection.MemberInfo | 成员信息 |
System.Reflection.EventInfo | 事件 |
System.Reflection.FieldInfo | 字段 |
System.Reflection.MethodBase | 基类方法 |
System.Reflection.ConstructorInfo | 构造函数 |
System.Reflection.MethodInfo | 方法 |
System.Reflection.PropertyInfo | 属性 |
System.Type | 类、对象的类型对象 |
反射是如何通过上述的Reflection命名空间的类与方法获取类和方法名的?主要依据了元数据(metadata),在程序高级语言中(C#)元数据的表现形式是一种二进制信息,用以对存储在公共语言运行库(CLR)可移动执行文件(PE)或者存储在内存中程序进行描述,编译器將代码编译成PE文件时便会將元数据插入到该文件的一部分,而将代码转换为 Microsoft 中间语言 (MSIL) 并将其插入到该文件的另一部分中,所以包含元数据和使用中间语言將代码生成的部分。元数据將存储如下信息,程序集(名称、版本、区域性、公钥)、类的说明(名称、可见性、基类和实现的接口)、类的成员(方法、字段、属性、事件、嵌套的类型)等。当执行代码的时候,运行库將元数据加载到内存中,并通过引用它(元数据)来发现有关代码的类、成员、继承等信息。
反射是审查元数据并收集关于它的类型信息的能力,元数据(编译后的最基本数据单元)一些表,当编译程序集货模块时,编译器会创建如下信息。1、关于程序集的元数据(清单)主要包含如下信息:标识信息(包括程序集的名称、版本、文化和公钥等);文件列表(程序集由哪些文件组成);引用程序集列表(该程序集所引用的其他程序集);一组许可请求(运行这个程序集需要的许可)。2、关于类型的元数据包含一个类定义表、一个字段定义表、一个方法定义表、方法参数表等,System.reflection命名空间包含的几个类,允许你反射(解析)这些元数据表的代码 。
三、使用
属性 | 用途 |
Assembly | 程序集,定义和加载程序集、程序集的模块清单、查找程序集的类和创建该类的实例对象 |
Module | 模块,获取模块上的程序集和类、获取模块的全局方法或者其他特定非全局方法 |
ConstructorInfo | 构造函数,获取其名称、参数、访问修饰符(public,private,protected)、实现信息(如abstract或virtual) |
MethodInfo | 方法,获取其名称、返回类型、参数、访问修饰符(public,private,protected)、实现信息(如abstract或virtual) |
FiedInfo | 字段,获取其名称、访问修饰符(public,private,protected)和实现信息(static);设置或获取字段的值。 |
EventInfo | 事件,获取其名称、事件处理程序数据类型、自定义属性、声明类型和反射类型;加入和移除事件。 |
PropertyInfo | 属性,获取其名称、数据类型、声明类型、反射类型和只读或可写状态等;设置或获取属性的值 |
ParameterInfo | 参数、获取其名称、数据类型、是输入参数还是输出参数,以及参数在方法签名中的位置等 |
使用反射获取上述内容来操作程序集、类、对象必须获取对象的类型对象(Type类型),获取的类型对象探测内部结构信息,通过查看Type类的定义(源代码)可以看到其继承接口IReflect,MemberInfo,_Type主要包含判断对象的类型,类中的成员,属性、方法的获取等,如下代码所示。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Reflection; namespace 反射 { /// <summary> /// 反射Demo /// </summary> class Program { static void Main(string[] args) { Assembly assembly = typeof(int).Assembly; // 获取类型对象的方式 var str = "Reflection"; var t1 = typeof(string);// 获取类的类型对象 var t2 = str.GetType();// 获取对象的类型对象 var t3 = Type.GetType("System.String"); // Type类的静态方法获取 // 通过以上获取类型对象来探测成员信息(所有的) foreach (var item in t1.GetMembers()) { // 输出成员类型(方法、属性、字段、构造函数)和名称 Console.WriteLine("{0}/t/t{1}", item.MemberType, item.Name); } var animalType = typeof(Animal); foreach (var item in animalType.GetMembers()) { // 输出自定义类的成员 Console.WriteLine("{0}/t/t{1}", item.MemberType, item.Name); } // 1、创建一个对象,获取对象类的方法,通过Invoke的方式执行方法(注意匹配参数) var catAnimal = new Animal("cat"); var eatMethod = animalType.GetMethod("Eat"); eatMethod.Invoke(catAnimal,new object[] { "food"}); // 2、通过构造函数创建对象(Activator远程创建对象),并且获取类型方法,使用对象执行方法 var pars = new object[] {"dog" }; object obj = Activator.CreateInstance(animalType, pars); var flyMethod = animalType.GetMethod("Fly"); flyMethod.Invoke(obj, null); // 3、通过Animal类的Type对象获取字段type,然后使用构造函数创建的对象赋值这个字段值,并且获取值 FieldInfo typeFieldInfo = animalType.GetField("type");// 如果字段设置是private修饰符,无法获取对象 typeFieldInfo.SetValue(obj, "猫科类"); Console.WriteLine("obj对象的字段type值{0}", typeFieldInfo.GetValue(obj)); // 4、通过Animal类的Type对象获取属性Name PropertyInfo namePropertyInfo = animalType.GetProperty("Name"); namePropertyInfo.SetValue(obj, "bird"); Console.WriteLine("obj对象的属性Name值{0}", namePropertyInfo.GetValue(obj)); Console.ReadKey(); } } /// <summary> /// 接口 /// </summary> public interface IAnimal { void Eat(string food); void Fly(); } /// <summary> /// 类 /// </summary> [Serializable] public class Animal : IAnimal { public string type; /// <summary> /// 属性 /// </summary> public string Name { get; set; } /// <summary> /// 默认构造函数 /// </summary> public Animal() { } /// <summary> /// 构造函数 /// </summary> /// <param name="name"></param> public Animal(string name) { Name = name; } /// <summary> /// 动物吃东西 /// </summary> /// <param name="food"></param> public void Eat(string food) { Console.WriteLine("动物{0}在吃{1}", Name, food); } /// <summary> /// 动物飞起来 /// </summary> public void Fly() { Console.WriteLine("动物{0}在飞", Name); } } }
通过System.Reflection.Assembly来动态加载程序集查找程序集的类,使用类创建对象实例,使用获取的对象实例访问方法、属性、字段等内容。Assembly assembly= Assembly.Load("程序集路径dll或者exe");方法加载程序集。assembly.CreateInstance("类的完全限定名称,命名空间.类名称")方法创建类实例,通过实例执行类操作。反射在实际开发中,比如对于数据的ORM,通过反射的方式把DataTable转换成关系对象;在执行方法需要依据实际情况进行选择的时候可以动态执行;在asp.net mvc的框架中路由、控制器、方法中也是广泛使用了反射。
四、总结
反射的优缺点,优点是提高程序的灵活性和扩展性;降低耦合,提高自适应能力;对任何类的无需硬编码在运行时进行绑定。缺点是性能问题,反射通过获取元数据的描述内容,查找,创建过程相对直接使用是慢的;反射的模糊程序内部逻辑,没有依据代码,直接获取对象信息影响代码的阅读和维护。
在实际开发中,由于其缺点问题,所以基本不怎么使用反射的功能,但是必须了解反射的机制,因为在很多封装的框架模块中都应用了反射的特性,对于源码的阅读理解有很大的帮助。
通过面试被问到反射的原理,基于目前知识面只是知道反射是什么,怎么使用的,因为这件事情促使进一步深究了解到程序集,元数据的内容,了解到反射实现机制。从而对整个知识串联起来,形成一个体系,融会贯通。所以技术的学习过程是一个深入探究的过程,形成体系的过程,融会贯通的过程。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?