如何实现自定义动态拦截器?基于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
相关产品推荐
相关产品推荐

