反射到底在反射什么
通常写代码,类型在编译期就是确定的:你 new 一个类、调它的方法,编译器都清楚。反射(Reflection)反过来——程序运行到某个时刻,才去「看」一个类型有哪些方法、属性、字段,再决定调用谁。就像一面镜子,把类型自身的结构照出来给程序看。
反射让「类型」从编译期的概念,变成了运行时可以操作的数据。
典型用途有三个:插件系统(动态加载程序集)、序列化(把对象字段逐个读出来转 JSON)、依赖注入容器(按约定自动装配)。这些场景的共同点是——编译期根本不知道要调用的具体类型是什么。
- 读拿到类型、方法、属性、参数的元数据。
- 建运行时按类型名创建对象实例。
- 调动态调用方法、读写属性值。
读取类型信息与动态创建对象
拿到 Type 对象是反射的起点:可以用 typeof,也可以从一个实例GetType()。有了 Type,就能枚举它的所有成员。
Type t = typeof(OrderService);
Console.WriteLine(t.Name);
foreach (var m in t.GetMethods())
Console.WriteLine("方法: " + m.Name);
// 动态创建对象
object obj = Activator.CreateInstance(t);
如果连程序集都是运行时才知道的,还可以用 Assembly.LoadFrom 加载一个外部 dll,再在里面按名字找类型。插件框架就是靠这一步实现「不重新编译主程序,只替换 dll 就加功能」。
特性:给类型打标签
特性(Attribute)是一种可以贴在类、方法、属性上的「标签」,本身不影响逻辑,但可以被反射读出来。比如给一个属性贴上「必填」「长度」标签,框架代码读这些标签就能自动做校验。
public class DisplayAttribute : Attribute {
public string Label { get; }
public DisplayAttribute(string label) { Label = label; }
}
public class Product {
[Display("商品名")]
public string Name { get; set; }
}
读取时,遍历属性,看它上面有没有这个特性,有就拿 Label 去自动生成表单标题。标注和处理分离,业务代码只管打标签,框架统一处理。
动态调用与性能权衡
拿到 MethodInfo 后,可以 Invoke 动态调用。但反射调用比直接调用慢不少——它要绕过编译期的检查,做参数匹配、空值处理。所以高频热路径上要慎用。
MethodInfo m = t.GetMethod("Calculate");
m.Invoke(obj, new object[] { 1, 2 });
优化办法是「缓存委托」:第一次用反射拿到 MethodInfo 后,编译成一个强类型委托缓存起来,之后直接调委托,速度接近原生。DI 容器、ORM 框架都是这么做的——第一次慢,之后飞快。
实战:一个简单对象映射器
把一个对象的同名属性复制到另一个对象——典型的反射场景。写一个通用函数,不用知道具体类型。
public static void Copy<T>(T src, T dst) {
var props = typeof(T).GetProperties();
foreach (var p in props) {
if (!p.CanRead || !p.CanWrite) continue;
var val = p.GetValue(src);
p.SetValue(dst, val);
}
}
这个小函数能服务所有类型,正是反射的价值:一份代码,适配成千上万种数据对象。ORM 的字段映射、对象到视图模型的转换,底层都是这个思路。
动态类型与 Emit 概览
C# 还有 dynamic 关键字:声明为 dynamic 的对象,编译器不检查它的成员,运行时才解析。写一些和脚本语言交互的胶水代码时很省事,但出错要到运行时才暴露,不能滥用。
再往下是 Emit——直接用代码在运行时「拼装」IL 指令,生成全新的类型。这是高性能 ORM、AOP 框架的杀手锏,但门槛极高。日常业务几乎用不到,知道有这回事即可。
dynamic x = GetFromScript();
x.DoSomething(12); // 编译期不检查,运行时解析
小结与延伸阅读
反射是把类型从编译期解放到运行时的钥匙:插件、序列化、DI、ORM 都建立在它之上。用它的边界很清晰——需要「未知类型」的地方用,热路径上缓存委托避免慢调用。
- DI 容器正是反射的典型用户,读《.NET 依赖注入与模块化架构实战》。
- 异步并发的写法,看《C# LINQ 与异步编程实战》。
- 语言底子回顾,读《C# 语言核心:.NET 开发基石》。