C# 反射不是“黑魔法”,而是对程序集元数据的读取和操作能力——它能让你在运行时知道一个类型长什么样、有哪些方法、怎么创建实例,但代价是明确的性能开销。
反射到底在干什么?本质是读元数据,不是反编译
你写的
C#代码编译后会生成
.dll或
.exe文件,里面除了中间语言(MSIL),还有一块叫「元数据」的二进制区域。它像一张静态说明书,记录了所有类名、方法签名、属性、字段、构造函数、特性(
Attribute)等信息。
反射(
System.Reflection)干的事,就是按需打开这张说明书,查表、解析、调用——它不执行反编译,也不还原源码,只读元数据。 ✅ 正确理解:反射 = 查类型说明书 + 按说明书动态干活 ❌ 常见误解:“反射能绕过
private” —— 实际上只能访问
private成员是因为
BindingFlags.NonPublic允许你“翻说明书里的隐藏条目”,不是突破访问限制 ⚠️ 注意:如果类型没被 JIT 编译过(比如从未实例化),它的元数据依然存在,反射照样能读到
Activator.CreateInstance
和 MethodInfo.Invoke
是性能重灾区
反射慢,主要慢在这两个环节:它们要走完整的“查找 → 绑定 → 安全检查 → 调用”链路,而直接 new 或方法调用是 JIT 编译后的一条机器指令。
✅Activator.CreateInstance(typeof(MyClass))比
new MyClass()慢 5–10 倍(无参构造);带参构造更慢,尤其参数类型匹配失败时还会抛异常 ✅
methodInfo.Invoke(obj, args)比直接调用慢 20–100 倍,取决于参数数量和是否需要装箱/拆箱 ✅ 性能敏感场景(如高频循环、实时服务)应避免在热路径中使用反射调用 ? 替代方案:缓存
ConstructorInfo/
MethodInfo实例;或用
Expression.Lambda编译成委托(一次编译,多次调用接近原生速度)
什么时候必须用反射?别为了“炫技”硬上
反射不是通用工具,它是为特定解耦场景设计的——当编译时完全不知道类型,又必须在运行时跟它打交道时,才值得引入。
✅ 插件系统:主程序加载Plugin.dll,通过约定接口名(如
ICommand)查找并实例化实现类 ✅ ORM 映射:
Dapper/
EF Core用反射读取实体类的
PropertyInfo,把数据库字段映射到对象属性 ✅ 序列化框架:
Newtonsoft.Json用反射遍历属性生成 JSON 字符串 ⚠️ 别滥用:比如只是想根据字符串名选一个已知的枚举值,用
Enum.Parse就够了,不需要
Type.GetType(...).GetMethod(...).Invoke(...)⚠️ 隐藏风险:反射调用可能因类型版本变化、命名空间变更、强名称校验失败而突然崩溃,且错误堆栈难读
真正该警惕的,不是“反射慢”,而是“反射让调用关系脱离代码可见性”——你改了一个私有字段名,编译器不报错,但某处反射代码就默默挂了。这种隐式依赖,比性能更难维护。
