获取类型对象反射起点是Type实例:编译期用typeof(不触发初始化),运行时用GetType()(对象非null);泛型需指定参数;值类型GetType()返回装箱后类型;查成员需注意BindingFlags;读值区分实例/静态;Invoke()参数须object[]且精确匹配签名,异常被包装。

如何用 typeof 和 GetType() 获取类型对象
反射的起点是拿到 Type 实例。最常用两种方式:编译期已知类型用 typeof,运行时对象用 GetType()。typeof 是编译时运算符,不触发类型初始化;GetType() 是实例方法,要求对象非 null。
-
typeof(string)直接获取Type,适用于你知道类型名的场景 -
"hello".GetType()返回System.String,但若变量为null会抛NullReferenceException - 泛型类型需注意:
typeof(List正确,) typeof(List)编译失败(缺少泛型参数) - 对于值类型,
GetType()总是返回装箱后的实际运行时类型,而typeof可直接写typeof(int)
用 Type.GetMembers() 和 Type.GetFields() 查看成员结构
拿到 Type 后,常要检查它公开了哪些字段、属性、方法。不同方法返回不同粒度的元数据,且默认只返回 public 成员,需显式传入 BindingFlags 才能访问 private 或静态成员。
-
type.GetFields()只返回字段(FieldInfo),不含属性;type.GetProperties()返回属性(PropertyInfo),不含字段 - 想查私有字段?必须加
BindingFlags.NonPublic | BindingFlags.Instance,否则返回空数组 -
type.GetMembers()是“大杂烩”,返回所有成员(字段、方法、属性、事件等),但不能直接读写值,适合做结构扫描 - 性能提示:这些方法每次调用都新建数组,频繁调用建议缓存结果,尤其在热路径中
通过 FieldInfo.GetValue() 和 PropertyInfo.GetValue() 读取值
反射读值的关键是区分“实例成员”和“静态成员”。对实例成员,GetValue() 第一个参数必须传入对应实例;对静态成员,传 null 即可。
- 读取实例字段:
fieldInfo.GetValue(obj),其中obj不能为null(除非字段是 static) - 读取属性:
propertyInfo.GetValue(obj),同样需传实例;若属性有 getter 逻辑,该逻辑会在反射调用时执行 - 读取静态字段或属性:第二个参数传
null,如staticField.GetValue(null) - 值类型字段读取后是装箱对象,需手动
Convert.ChangeType()或(int)val强转——但要注意null值和可空类型(int?)的处理,否则抛InvalidCastException
反射调用方法时 MethodInfo.Invoke() 的常见陷阱
Invoke() 看似简单,但参数传递、重载解析、异常包装这三点最容易出错。
- 参数必须是
object[],哪怕方法只有一个int参数,也要写成new object[] { 42 };传int本身会报错 - 重载方法必须精确匹配签名,
Invoke()不会自动转换参数类型(比如传long调用void M(int)会失败) - 被调方法抛出的异常会被包装进
TargetInvocationException,原始异常在.InnerException里,不展开就看不到真实错误 - 性能极低:比直接调用慢 50–100 倍。如果需要高频调用,考虑用
Delegate.CreateDelegate()缓存委托,或用Expression构建强类型调用器
反射不是黑魔法,它把编译期绑定推迟到运行时,代价是类型安全丢失、调试困难、性能下降。真正要用时,优先确认是否真无法用接口、泛型约束或 Source Generator 替代;一旦选了反射,务必对 null、装箱、BindingFlags 组合、异常包装这几个点保持警惕。









