如何通过指定路径修改对象字段?能否预定义路径批量赋值?
优雅批量修改C#对象字段的实现方案
问题根源
你尝试用Func<Item, int>集合批量处理属性,但Func只能读取属性值,无法作为赋值语句的左值,因此触发CS0131错误。要实现批量修改,需要同时封装属性读取和属性赋值的操作。
解决方案
方案1:用ValueTuple封装读写操作(最直接)
直接定义包含读取器(Getter)和写入器(Setter)的元组集合,循环中完成计算与赋值:
class Item { public int Value1 { get; set; } public int Value2 { get; set; } public int Value3 { get; set; } public int Value4 { get; set; } } var targetItem = new Item(); var common1 = new Item(); var common2 = new Item(); // 定义每个属性的读写操作 var propertyOps = new List<(Func<Item, int> Getter, Action<Item, int> Setter)> { (i => i.Value1, (i, val) => i.Value1 = val), (i => i.Value2, (i, val) => i.Value2 = val), (i => i.Value3, (i, val) => i.Value3 = val), (i => i.Value4, (i, val) => i.Value4 = val) }; foreach (var op in propertyOps) { var newValue = Do(op.Getter); op.Setter(targetItem, newValue); } int Do(Func<Item, int> func) { // 复杂业务逻辑 return func(common1) + func(common2); }
方案2:自定义Accessor类(更清晰)
封装读写逻辑为独立类,提升代码可读性和复用性:
public class PropertyAccessor<TObj, TProp> { public Func<TObj, TProp> Get { get; } public Action<TObj, TProp> Set { get; } public PropertyAccessor(Func<TObj, TProp> get, Action<TObj, TProp> set) { Get = get; Set = set; } } // 使用示例 var accessors = new List<PropertyAccessor<Item, int>> { new PropertyAccessor<Item, int>(i => i.Value1, (i, val) => i.Value1 = val), new PropertyAccessor<Item, int>(i => i.Value2, (i, val) => i.Value2 = val), new PropertyAccessor<Item, int>(i => i.Value3, (i, val) => i.Value3 = val), new PropertyAccessor<Item, int>(i => i.Value4, (i, val) => i.Value4 = val) }; foreach (var accessor in accessors) { var newValue = Do(accessor.Get); accessor.Set(targetItem, newValue); }
方案3:表达式树自动生成Setter(最简洁)
利用表达式树自动生成Setter,避免手动编写赋值逻辑,适合大量属性的场景:
using System.Linq.Expressions; public static class PropertyAccessorHelper { public static PropertyAccessor<TObj, TProp> Create<TObj, TProp>(Expression<Func<TObj, TProp>> propertyExpr) { var getFunc = propertyExpr.Compile(); // 动态生成赋值表达式 var objParam = Expression.Parameter(typeof(TObj)); var valParam = Expression.Parameter(typeof(TProp)); var assignExpr = Expression.Assign( Expression.Property(objParam, ((MemberExpression)propertyExpr.Body).Member), valParam ); var setAction = Expression.Lambda<Action<TObj, TProp>>(assignExpr, objParam, valParam).Compile(); return new PropertyAccessor<TObj, TProp>(getFunc, setAction); } } // 使用示例 var accessors = new List<PropertyAccessor<Item, int>> { PropertyAccessorHelper.Create<Item, int>(i => i.Value1), PropertyAccessorHelper.Create<Item, int>(i => i.Value2), PropertyAccessorHelper.Create<Item, int>(i => i.Value3), PropertyAccessorHelper.Create<Item, int>(i => i.Value4) }; foreach (var accessor in accessors) { var newValue = Do(accessor.Get); accessor.Set(targetItem, newValue); }
方案4:反射(动态场景)
如果需要动态指定属性名,可使用反射实现,但性能略低于表达式树:
var propertyNames = new List<string> { "Value1", "Value2", "Value3", "Value4" }; var itemType = typeof(Item); foreach (var propName in propertyNames) { var property = itemType.GetProperty(propName); if (property == null || property.PropertyType != typeof(int)) continue; var val1 = (int)property.GetValue(common1); var val2 = (int)property.GetValue(common2); var newValue = val1 + val2; property.SetValue(targetItem, newValue); }
选择建议
- 属性数量少且固定:用ValueTuple方案,代码最简洁直接。
- 需要复用读写逻辑:用自定义Accessor类,结构更清晰。
- 属性数量多或想减少重复代码:用表达式树方案,自动生成Setter。
- 需要动态处理属性:用反射方案,支持运行时指定属性名。
内容的提问来源于stack exchange,提问作者ilyaVlad
相关产品推荐
相关产品推荐

