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

如何实现自定义动态拦截器?基于Dictionary的动态属性解析需求咨询

当然可行!用DynamicObject轻松实现自定义动态类型

你的需求完全可以通过C#的DynamicObject类来实现,它就是为这类自定义动态成员解析场景设计的——既能让你像调用普通POCO属性那样访问字典数据,又能完全掌控键的匹配逻辑,还能统一处理不存在的属性情况,彻底干掉那些重复的字典样板代码。

实现步骤与完整示例

1. 定义自定义异常(可选但推荐)

先搞个自定义异常,方便后续抛出更贴合业务的错误信息:

public class DynamicPropertyNotFoundException : Exception
{
    public DynamicPropertyNotFoundException(string propertyName) 
        : base($"动态属性 '{propertyName}' 未找到或匹配不到对应的字典键")
    {
        PropertyName = propertyName;
    }

    public string PropertyName { get; }
}

2. 实现自定义动态类型

继承DynamicObject,内部持有字典实例,重写TryGetMember方法来实现自定义解析逻辑:

public class MyDynamicType : DynamicObject
{
    private readonly Dictionary<string, string> _data;

    private MyDynamicType(Dictionary<string, string> data)
    {
        _data = data ?? throw new ArgumentNullException(nameof(data));
    }

    // 提供Build方法初始化
    public static MyDynamicType Build(Dictionary<string, string> data)
    {
        return new MyDynamicType(data);
    }

    // 实现隐式转换,让字典可以直接转成动态类型(更便捷)
    public static implicit operator MyDynamicType(Dictionary<string, string> data)
    {
        return new MyDynamicType(data);
    }

    // 核心:自定义成员解析逻辑
    public override bool TryGetMember(GetMemberBinder binder, out object result)
    {
        var propertyName = binder.Name;
        result = null;

        // 这里可以根据需求扩展任意匹配规则,示例包含三种常用规则:
        // 规则1:优先精确匹配
        if (_data.TryGetValue(propertyName, out var exactValue))
        {
            result = exactValue;
            return true;
        }

        // 规则2:忽略大小写匹配
        var caseInsensitiveMatch = _data.FirstOrDefault(kv => 
            string.Equals(kv.Key, propertyName, StringComparison.OrdinalIgnoreCase));
        if (!string.IsNullOrEmpty(caseInsensitiveMatch.Value))
        {
            result = caseInsensitiveMatch.Value;
            return true;
        }

        // 规则3:忽略空格(比如把"Member Property"转成"MemberProperty"匹配)
        var propertyNameWithoutSpaces = propertyName.Replace(" ", "");
        var spaceIgnoredMatch = _data.FirstOrDefault(kv => 
            string.Equals(kv.Key.Replace(" ", ""), propertyNameWithoutSpaces, StringComparison.OrdinalIgnoreCase));
        if (!string.IsNullOrEmpty(spaceIgnoredMatch.Value))
        {
            result = spaceIgnoredMatch.Value;
            return true;
        }

        // 所有规则都不匹配时,抛自定义异常
        throw new DynamicPropertyNotFoundException(propertyName);
    }
}

3. 使用示例

现在你可以像用普通POCO一样调用动态类型的属性了:

// 模拟产品人员配置的字典(可能有大小写、空格差异)
var configData = new Dictionary<string, string>
{
    {"MemberProperty", "测试值1"},
    {"memberproperty", "测试值2"}, // 大小写不同
    {"Member Property", "测试值3"}  // 带空格
};

// 方式1:用Build方法初始化
dynamic dynamicObj1 = MyDynamicType.Build(configData);
Console.WriteLine(dynamicObj1.MemberProperty); // 输出"测试值1"(精确匹配优先)
Console.WriteLine(dynamicObj1.memberProperty); // 输出"测试值2"(忽略大小写匹配)

// 方式2:用隐式转换,写法更简洁
dynamic dynamicObj2 = configData;
Console.WriteLine(dynamicObj2.MemberProperty); // 输出"测试值1"
Console.WriteLine(dynamicObj2.Member_Property); // 会抛DynamicPropertyNotFoundException

// 测试不存在的属性
try
{
    var val = dynamicObj2.PropertyThatDoesntExist;
}
catch (DynamicPropertyNotFoundException ex)
{
    Console.WriteLine(ex.Message); // 输出:动态属性 'PropertyThatDoesntExist' 未找到或匹配不到对应的字典键
}

关键逻辑说明

  • TryGetMember方法是动态成员访问的核心入口,binder.Name就是你调用的属性名
  • 你可以完全自定义匹配规则:比如支持下划线转驼峰、缩写匹配,甚至正则匹配——只要在这个方法里实现逻辑就行
  • 如果所有规则都不匹配,直接抛出你的自定义异常,替代默认的RuntimeBinderException
  • 隐式转换的实现让字典转动态类型的操作更自然,减少冗余代码

适配你的应用场景

对于产品人员动态配置的字段,这个方案可以帮你:

  • 规避人为输入的大小写、空格等格式错误
  • 把所有键匹配逻辑集中在一个类里,避免到处写TryGetValue和重复的检查代码
  • 统一处理属性不存在的情况,抛出更友好的业务异常

内容的提问来源于stack exchange,提问作者Bigsby

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 07:30:07