反射到底在反射什么

通常写代码,类型在编译期就是确定的:你 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 的字段映射、对象到视图模型的转换,底层都是这个思路。