反射的基础用法与核心避坑
最近在项目开发中接触到了反射(Reflection),在看一些底层公共组件和基础框架时经常能遇到它。为了加深理解,我把反射的核心基础用法、实际应用场景以及需要注意的性能隐患做了一次梳理和记录。
一、 什么是反射?
在 C# 中,常规编写代码是在编译期就确定了类型和调用关系;而反射则是提供了一种在运行期动态窥探和操作程序集的能力。
程序运行起来后,其对应的类、方法、属性等元数据都会保存在程序集中。通过反射,我们可以在程序运行的过程中动态地获取这些类型信息,甚至直接实例化对象、调用里面的方法或读写属性。
二、 获取 Type 对象的三种基础用法
在 C# 中,使用反射的第一步永远是拿到目标类的 Type 对象。通常有以下三种最基础的获取方式:
1. 用法一:typeof(类名)
- 说明:在编译期就已经明确知道要获取哪个类的类型信息。
- 示例:
1
Type t = typeof(User);
2. 用法二:obj.GetType()
- 说明:手里已经有了具体的对象实例,在运行期通过实例来获取其真实的类型信息。
- 示例:
1
2User user = new User();
Type t = user.GetType(); - 特点:常用于编写通用工具方法,通过接收 object 对象来动态判断其具体类型。
3. 用法三:Type.GetType("全限定类名")
- 说明:通过类的完整名称字符串(命名空间+类名)进行动态加载。
- 示例:
1
Type t = Type.GetType("MyProject.Models.User");
- 特点:灵活性最强。类名是一个纯字符串,可以从配置文件中读取,从而实现代码的解耦。即使代码中没有直接引用这个类,也能在运行时动态加载。
三、 反射的核心应用场景
拿到Type对象后,在实际开发中通常有以下三大核心操作:
1. 动态创建对象
不需要通过 new 关键字,直接在运行时根据 Type 实例化对象:
1 | Type t = Type.GetType("MyProject.Models.User"); |
2. 动态读取和修改属性
当需要遍历一个未知实体类的所有字段或属性时(例如做通用的数据导出或属性映射),可以使用反射:
1 | PropertyInfo[] properties = t.GetProperties(); |
3. 动态调用方法
在不知道具体类型的情况下,通过方法名字符串动态执行其方法:
1 | MethodInfo method = t.GetMethod("CalculateSalary"); |
四、 反射的缺点与避坑指南
反射虽然功能强大,但也伴随着明显的代价,在实际使用中需要保持克制:
1. 明显的性能开销
普通的代码调用在编译后是非常直接的 CPU 指令,而通过反射调用时,CLR 必须去检索程序集的元数据表,进行字符串匹配、安全权限检查等一系列操作。
- 避坑:反射的执行速度比原生代码慢很多。因此,在高频调用的核心业务循环(如每秒几万次的传感器数据轮询)中,绝对不能使用反射。
2. 编译器失去安全检查
如果是普通写法,方法名写错(如少个字母),在编译阶段 Visual Studio 就会直接报错报错提示。 而如果使用反射:t.GetMethod("Claculate")(拼写错误),编译器在编译时完全不会报错。程序能正常跑起来,直到运行到这一行时才会抛出 NullReferenceException 导致系统崩溃,隐蔽性极高。
五、 个人心得总结
- 框架的灵活性是有代价的:像我们平时用的许多第三方 ORM 框架、依赖注入(DI)容器或对象映射工具(AutoMapper),底层都重度依赖反射。为了通用性和灵活性,框架层牺牲部分性能是合理的,但在业务层编写代码时,能不用就尽量不用。
- 善用缓存优化性能:如果项目中有些地方(如读取自定义 Attribute 特性)非用反射不可,可以考虑利用
Dictionary将第一次反射拿到的Type或PropertyInfo缓存起来。后续直接从字典里取,避免重复翻阅元数据,这样能大幅降低反射带来的性能损耗。 - 自我思考:以前觉得很多框架的配置化操作很神秘,搞懂反射的底层机制后,再去看那些动态加载的代码,思路就会清晰很多。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 小夜博客!
评论





