如何在C#中实现仿JS Object.assign的可选可重写配置类
在C#中实现类似JavaScript的动态对象覆盖默认配置功能
需求说明
在JavaScript中,可通过Object.assign传入动态对象,仅覆盖目标对象的指定成员,其余保留默认值。示例代码如下:
class Options { constructor() { this.foo = 123; this.fee = false; this.nothing = 1.23; this.thing = new MouseEvent(); } } var globalOps = new Options(); function doTest(ops = null) { var currentOps = Object.assign({}, globalOps); // 克隆避免修改全局配置 if(ops != null) Object.assign(currentOps, ops); console.log(currentOps.foo); // 输出555 - 默认值123 console.log(currentOps.fee); // 输出true - 默认值false console.log(currentOps.nothing); // 输出1.23 - 保持默认 console.log(currentOps.thing); // 输出null - 默认是MouseEvent实例 } globalOps.fee = true; doTest({ foo: 555, thing: null });
提问者尝试过多种方案但均不理想,希望找到C#中类型安全、有智能提示、兼容.NET Framework(或适配高版本.NET)、可读性强的实现方式。
解决方案
方案1:匿名类型+反射赋值(兼容.NET Framework)
利用匿名类型的强类型特性(自带智能提示),结合反射将匿名对象的属性值复制到默认配置的克隆实例上,完美匹配需求:
1. 定义配置类
public class Options { public int Foo { get; set; } = 123; public bool Fee { get; set; } = false; public double Nothing { get; set; } = 1.23; public MouseEvent Thing { get; set; } = new MouseEvent(); // 实现浅克隆,避免修改全局实例 public Options Clone() { return (Options)MemberwiseClone(); } }
2. 编写反射扩展方法
public static class ObjectExtensions { // 将匿名对象的属性值复制到目标对象 public static T ApplyOverrides<T>(this T target, object overrides) where T : class { if (overrides == null) return target; var targetType = typeof(T); var overrideType = overrides.GetType(); foreach (var overrideProp in overrideType.GetProperties()) { var targetProp = targetType.GetProperty(overrideProp.Name); if (targetProp != null && targetProp.CanWrite) { var value = overrideProp.GetValue(overrides); targetProp.SetValue(target, value); } } return target; } }
3. 使用示例
// 全局配置实例 public static Options GlobalOps = new Options(); public void DoTest(object overrides = null) { // 克隆全局配置,再应用覆盖项 var currentOps = GlobalOps.Clone().ApplyOverrides(overrides); Console.WriteLine(currentOps.Foo); // 输出555 Console.WriteLine(currentOps.Fee); // 输出true(全局已修改) Console.WriteLine(currentOps.Nothing); // 输出1.23 Console.WriteLine(currentOps.Thing); // 输出null } // 调用方式(匿名类型有智能提示) GlobalOps.Fee = true; DoTest(new { Foo = 555, Thing = (MouseEvent)null });
优势:兼容.NET Framework,无额外依赖,调用语法接近JavaScript,智能提示完善。
方案2:记录类型With表达式(.NET 6+)
如果使用.NET 6及以上版本,可利用记录类型的With表达式实现不可变配置的部分更新,语法简洁且类型安全:
1. 定义记录类型配置
public record Options { public int Foo { get; init; } = 123; public bool Fee { get; init; } = false; public double Nothing { get; init; } = 1.23; public MouseEvent Thing { get; init; } = new MouseEvent(); }
2. 使用示例
// 全局配置实例 public static Options GlobalOps = new Options(); public void DoTest(Options overrides = null) { // 直接基于全局配置创建新实例,仅覆盖指定成员 var currentOps = overrides == null ? GlobalOps : GlobalOps with { Foo = overrides.Foo, Thing = overrides.Thing }; Console.WriteLine(currentOps.Foo); Console.WriteLine(currentOps.Fee); Console.WriteLine(currentOps.Nothing); Console.WriteLine(currentOps.Thing); } // 调用方式 GlobalOps = GlobalOps with { Fee = true }; DoTest(new Options { Foo = 555, Thing = null }); // 更简洁的调用:直接基于全局配置修改 DoTest(GlobalOps with { Foo = 555, Thing = null });
优势:语法简洁,原生支持不可变对象更新,类型安全。
方案3:Builder模式(适合复杂配置)
若配置项较多或需要更灵活的配置逻辑,Builder模式是最优选择,链式调用体验好,可读性强:
1. 定义配置类与Builder
public class Options { public int Foo { get; set; } = 123; public bool Fee { get; set; } = false; public double Nothing { get; set; } = 1.23; public MouseEvent Thing { get; set; } = new MouseEvent(); // 私有构造,强制通过Builder创建实例 private Options() { } // 浅克隆方法 public Options Clone() { return (Options)MemberwiseClone(); } public class Builder { private readonly Options _options; // 基于基础配置初始化Builder public Builder(Options baseOptions) { _options = baseOptions.Clone(); } // 链式设置方法 public Builder SetFoo(int value) { _options.Foo = value; return this; } public Builder SetThing(MouseEvent value) { _options.Thing = value; return this; } // 可添加更多Set方法适配其他属性 // 构建最终配置 public Options Build() { return _options; } } }
2. 使用示例
// 全局配置实例 public static Options GlobalOps = new Options(); public void DoTest(Action<Options.Builder> configure = null) { var builder = new Options.Builder(GlobalOps); configure?.Invoke(builder); var currentOps = builder.Build(); Console.WriteLine(currentOps.Foo); Console.WriteLine(currentOps.Fee); Console.WriteLine(currentOps.Nothing); Console.WriteLine(currentOps.Thing); } // 调用方式(链式调用,智能提示完善) GlobalOps.Fee = true; DoTest(builder => builder.SetFoo(555).SetThing(null));
优势:类型安全,扩展性强,适合复杂配置场景,代码可读性极高。
内容的提问来源于stack exchange,提问作者John
相关产品推荐
相关产品推荐

