C#如何实现仅接受指定类属性作为键的字典?
在C#中实现类似TypeScript Partial的编译时属性更新检查
你想要的是编译时就阻止传入目标类不存在的属性名,替代字典的弱类型方案,C#里没有直接对应TypeScript Partial 的内置类型,但可以通过以下几种强类型方式实现:
1. 匿名类型 + 反射(兼顾易用性与基本检查)
让Update方法接收匿名类型作为参数,匿名类型的属性名需与目标类一致(拼写错误能通过静态分析工具快速识别,运行时也会拦截非法属性),再通过反射映射属性值:
public static void Update<T>(T target, object partial) where T : class { var targetType = typeof(T); var partialProps = partial.GetType().GetProperties(); foreach (var prop in partialProps) { var targetProp = targetType.GetProperty(prop.Name); if (targetProp == null) throw new ArgumentException($"属性 {prop.Name} 不存在于 {targetType.Name} 中"); if (targetProp.PropertyType.IsAssignableFrom(prop.PropertyType)) targetProp.SetValue(target, prop.GetValue(partial)); } } // 合法使用:编译通过,运行时正常更新 Update(simpleClassInstance, new { a = 28, b = 82 }); // 非法使用:编译能通过,但运行时抛出异常(可配合静态分析工具提前拦截) // Update(simpleClassInstance, new { d = 28 });
2. 表达式树(严格编译时检查)
通过表达式树指定要更新的属性,完全在编译阶段就阻止不存在的属性:
public static class Updater { private static readonly Dictionary<Type, Dictionary<string, Action<object, object>>> _setterCache = new(); public static void Update<T>(T target, params (Expression<Func<T, int>> Property, int Value)[] updates) where T : class { var type = typeof(T); if (!_setterCache.ContainsKey(type)) _setterCache[type] = new Dictionary<string, Action<object, object>>(); foreach (var (expr, value) in updates) { var memberExpr = (MemberExpression)expr.Body; var propName = memberExpr.Member.Name; if (!_setterCache[type].TryGetValue(propName, out var setter)) { var prop = type.GetProperty(propName); var objParam = Expression.Parameter(typeof(object)); var valueParam = Expression.Parameter(typeof(object)); var objCast = Expression.Convert(objParam, type); var valueCast = Expression.Convert(valueParam, prop.PropertyType); var assignExpr = Expression.Assign(Expression.Property(objCast, prop), valueCast); setter = Expression.Lambda<Action<object, object>>(assignExpr, objParam, valueParam).Compile(); _setterCache[type][propName] = setter; } setter(target, value); } } } // 合法使用:编译通过 Updater.Update(simpleClassInstance, (x => x.a, 28), (x => x.b, 82)); // 非法使用:直接编译失败,因为simpleClass没有d属性 // Updater.Update(simpleClassInstance, (x => x.d, 28));
3. Record类型的with表达式(适用于不可变对象)
如果目标类可以改为Record类型,利用Record的with表达式实现属性更新,生成新实例,天然支持编译时属性检查:
// 将类改为Record public record SimpleClass(int a, int b, int c); // 合法使用:编译通过,生成新实例 var original = new SimpleClass(1, 2, 3); var updated = original with { a = 28, b = 82 }; // 非法使用:直接编译失败 // var invalid = original with { d = 28 };
4. 源代码生成器生成Partial类(最接近TypeScript体验)
通过源代码生成器自动为目标类生成包含所有可选属性的Partial类,Update方法接收这个类的实例,完全编译时约束:
// 自动生成的Partial类(通过源代码生成器实现) public class SimpleClassPartial { public int? a { get; set; } public int? b { get; set; } public int? c { get; set; } } // 自定义Update方法 public static void Update(SimpleClass target, SimpleClassPartial partial) { if (partial.a.HasValue) target.a = partial.a.Value; if (partial.b.HasValue) target.b = partial.b.Value; if (partial.c.HasValue) target.c = partial.c.Value; } // 合法使用:编译通过 Update(simpleClassInstance, new SimpleClassPartial { a = 28, b = 82 }); // 非法使用:直接编译失败 // Update(simpleClassInstance, new SimpleClassPartial { d = 28 });
内容的提问来源于stack exchange,提问作者Mark
相关产品推荐
相关产品推荐

