PropertyInfo.SetValue 可赋值于含 set 访问器的属性(含 private set),但需依访问级别指定 BindingFlags 或调用 SetAccessible(true);对只读属性则无法赋值,强行改背后字段属未定义行为。

用 PropertyInfo.SetValue 给私有/公有属性赋值
只要属性有 set 访问器(哪怕被标记为 private set),就能用反射写入。关键不是“能不能”,而是“要不要绕过访问修饰符”——SetValue 默认只操作 public 属性;若目标是 private 或 internal,必须先调用 SetAccessible(true)(.NET 5+)或用 BindingFlags.NonPublic(旧版)。
常见错误:直接调用 propertyInfo.SetValue(obj, value) 却没指定 binding flags,结果静默失败(尤其对 private 属性)。
- 对 public 属性:传
BindingFlags.Public | BindingFlags.Instance即可 - 对 private 属性:必须加
BindingFlags.NonPublic,且不能漏掉BindingFlags.Instance - .NET 5+ 可用
propertyInfo.SetAccessible(true)简化,但仅限当前PropertyInfo实例有效 - 如果属性是只读(无 set),
SetValue会抛InvalidOperationException
处理值类型、泛型和可空类型的赋值兼容性
反射赋值时,类型不匹配是高频崩溃点。例如把 int 赋给 int? 属性,或把 string 强转成 DateTime,SetValue 不做自动转换,会直接抛 ArgumentException。
真正安全的做法是:在调用 SetValue 前,手动做一次类型适配。
- 检查
propertyInfo.PropertyType.IsAssignableFrom(value.GetType())—— 但注意这不覆盖隐式转换(如int → long) - 对可空类型,用
Nullable.GetUnderlyingType()判断是否需要装箱;value == null && propertyInfo.PropertyType.IsNullable()才允许写入 null - 字符串转基础类型?别依赖反射,先用
Convert.ChangeType(value, propertyInfo.PropertyType),它会触发IConvertible或类型转换器 - 泛型类的属性(如
List<t></t>)没问题,但注意SetValue的 value 必须是运行时实际类型,不能是object包装的错误实例
批量赋值时避免反复查找 PropertyInfo 导致性能崩盘
每次调用 obj.GetType().GetProperty("Name") 都触发字典查找 + 元数据解析,在循环里做就是灾难。实测 10 万次反射查属性比直接字段访问慢 200 倍以上。
核心对策是缓存 PropertyInfo 对象,而不是缓存名字或类型名。
- 用
ConcurrentDictionary缓存,key 是(obj.GetType(), "PropertyName") - 不要缓存
Type.GetProperty的结果到静态字段再复用——不同泛型实例(如List<int></int>和List<string></string>)是不同 Type - 如果对象生命周期短但属性访问密集(如 DTO 映射),考虑用
Expression.Lambda编译一次 setter 委托,后续调用接近原生速度 - 第三方库如
FastMember就是干这事的,ObjectAccessor.Create(obj).["Prop"] = value内部已优化好路径
修改只读属性(无 set 访问器)的现实约束
严格来说,C# 反射无法真正“给只读属性赋值”——因为只读属性(public int Id { get; })编译后根本没生成 set 方法元数据,GetProperty 返回的 PropertyInfo 的 SetMethod 为 null,调用 SetValue 必抛异常。
有人用 FieldInfo 直接改背后字段(<id>k__BackingField</id>)来绕过,但这属于未定义行为:
- 字段名由编译器生成,不同版本 C# 或开启优化后可能变化
- auto-property 的 backing field 在 .NET 6+ 可能被内联或消除(尤其
init属性) - struct 的只读属性改字段会导致复制语义错乱
- 除非你完全控制代码生成(如 ORM 框架内部),否则线上环境禁止这么干
真正需要修改只读状态的场景,应该回到设计层面:用 init 属性 + with 表达式,或引入专门的内部构造函数 / builder 模式,而不是靠反射撕开封装。


















