必须显式指定BindingFlags.NonPublic才能通过FieldInfo.SetValue修改私有字段,否则GetField返回null导致NullReferenceException;还需匹配BindingFlags.Instance/Static,并避免BindingFlags.DeclaredOnly以访问父类私有字段。
用 FieldInfo.SetValue 修改私有字段前必须设 BindingFlags.NonPublic
反射访问私有成员不是默认开启的,不显式指定绑定标志,gettype().getfield 会直接返回 null,后续调用 setvalue 必然抛出 nullreferenceexception。
常见错误是只写 GetField("fieldName"),没传 BindingFlags 参数,结果字段找不到,还以为对象结构不对。
-
GetField和GetFields都要加BindingFlags.NonPublic | BindingFlags.Instance(静态字段用BindingFlags.Static) - 如果类有继承,且目标字段在父类里,还得加
BindingFlags.DeclaredOnly吗?不加——反而要**去掉**它,否则查不到父类声明的私有字段 - .NET Core/.NET 5+ 对反射权限更严格,某些 AOT 或 trimmed 场景下可能被裁剪掉私有成员元数据,得配
TrimmerRootAssembly或[DynamicDependency]
SetValue 报错 “Object does not match target type” 怎么办
这个错误不是类型转换问题,而是你传给 SetValue 的第一个参数(实例对象)和字段所属类型不匹配。比如字段定义在 class A 里,你却把 new B() 传进去。
另一个高频原因是:字段是 readonly,但你在 .NET 5+ 上没关掉运行时保护。此时 SetValue 会静默失败或抛异常,取决于运行时配置。
- 检查字段声明所在的类型:
fieldInfo.DeclaringType是否等于你传入对象的GetType() - 修改
readonly字段前,先调用fieldInfo.SetValue(obj, value)前加一句fieldInfo.IsInitOnly == false || RuntimeHelpers.InitializeArray(...)不行——正确做法是用fieldInfo.SetValue(obj, value)并捕获FieldAccessException,然后确认是否启用了System.Runtime.CompilerServices.UnsafeAccessor特性(仅限高级场景) - 更稳妥的方式:改用
Unsafe.AsRef直接操作内存(需unsafe上下文,且仅适用于值类型、布局确定的类)
用 GetProperty 访问私有属性比字段更麻烦
属性本质是方法(get_XXX/set_XXX),所以不能靠 FieldInfo,得走 PropertyInfo + GetMethod / SetMethod,而且私有 set 方法默认不可见。
最简路径是绕过属性,直接操作背后支持字段(如果存在且命名规范,比如 ),但这是编译器生成约定,不保证稳定。
-
GetProperty("Name", BindingFlags.NonPublic | BindingFlags.Instance)能拿到PropertyInfo,但propertyInfo.SetMethod可能为null(私有 set 被优化掉或没生成) - 若
SetMethod存在,调用setMethod.Invoke(obj, new object[]{value})即可,无需额外 flag - 注意自动属性的 backing field 名称在不同编译器版本中可能变化,别硬编码解析;调试时可用
obj.GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Instance)看实际字段名
性能差、热更新不友好、序列化易断裂——这些不是警告,是事实
反射调用比直接访问慢一个数量级以上,尤其在 tight loop 里反复调用 GetValue,JIT 很难优化。而且每次调用都要做权限检查、类型校验、栈帧展开。
更隐蔽的问题是:代码混淆工具(如 ILLink、Costura)默认会剔除未显式引用的私有成员;Unity IL2CPP 构建时也可能丢掉无引用的私有字段元数据。
- 高频访问场景务必缓存
FieldInfo或PropertyInfo实例,别每次临时GetField - 考虑用表达式树预编译委托:
Expression.Lambda,首次慢,之后接近原生速度>(Expression.Call(instanceParam, setterMethod, valueParam), instanceParam, valueParam).Compile() - 如果只是为了测试或 DI 注入,优先用构造函数参数或
[Inject]标记,而不是反射塞私有字段
真正难的不是怎么写那几行反射代码,是怎么判断“非得用它不可”——多数时候,设计上留个 internal setter 或加个测试友好的构造函数,比后期硬撬私有成员干净得多。










