C# 反射使用方法 C#如何通过反射获取类型信息

3次阅读

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

C# 反射使用方法 C#如何通过反射获取类型信息

如何用 typeofGetType() 获取类型对象

反射的起点是拿到 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 组合、异常包装这几个点保持警惕。

text=ZqhQzanResources