You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 12:18:31