C#实现含通配符的下划线分隔配置字符串通用匹配方案
问题背景
假设我有一个字符串列表,列表内的字符串格式分为三类:
property1property1_property2property1_property2_property3
这个列表的作用是和指定属性集拼接出的字符串做匹配校验,初始示例代码如下:
public List<string> EnabledSettings = new List<string> { "pizza_milk_iceCream", "meatballs_water_slushy", "sallad_tea_candy" };
我针对不同匹配维度定义了对应的枚举和校验类:
public enum Food { Pizza, Meatballs, Sallad, Soup } public enum Drink { Milk, Beer, Water, Tea } public enum Dessert { IceCream, Slushy, Cookies, Candy } public class FeatureOne { public List<string> EnabledSettings = new List<string> { "pizza_milk_iceCream", "meatballs_water_slushy", "sallad_tea_candy" }; public bool IsEnabled(Food food, Drink drink, Dessert dessert) => EnabledSettings.Contains($"{food}_{drink}_{dessert}"); } public class FeatureTwo { public List<string> EnabledSettings = new List<string> { "pizza_milk", "meatballs_water", "sallad_tea" }; public bool IsEnabled(Food food, Drink drink) => EnabledSettings.Contains($"{food}_{drink}"); }
现在需要给匹配逻辑增加通配符支持,允许配置中使用*匹配任意对应位置的属性,比如property1_*_property3、property1_property2_*这类格式都要能正确命中。
现有思路
我目前的初步实现方向是:让每个特性类定义自身用到的匹配字段,标记字段是否支持可选匹配,再基于这些字段生成所有可能的排列组合,最后和外部传入的启用配置列表做比对,初步代码框架如下:
public record SettingField(string Value, bool Optional); public enum Food { Pizza, Meatballs, Sallad, Soup } public enum Drink { Milk, Beer, Water, Tea } public enum Dessert { IceCream, Slushy, Cookies, Candy } public class FeatureOne { public List<string> EnabledSettings = new List<string> { "pizza_milk_iceCream", "pizza_beer_*", "meatballs_water_slushy", "meatballs_*_cookies", "sallad_tea_candy" }; public bool IsEnabled(Food food, Drink drink, Dessert dessert) { var fields = new List<SettingField> { new SettingField(food.ToString(), false), new SettingField(drink.ToString(), true), new SettingField(dessert.ToString(), true) }; var variants = new List<string>(); // 基于上述字段生成所有可能的排列组合 return variants.Any(x => EnabledSettings.Contains(x)); } } public class FeatureTwo { public List<string> EnabledSettings = new List<string> { "pizza_milk", "meatballs_water", "sallad_tea", "soup_*" }; public bool IsEnabled(Food food, Drink drink) { var fields = new List<SettingField> { new SettingField(food.ToString(), false), new SettingField(drink.ToString(), true) }; var variants = new List<string>(); // 基于上述字段生成所有可能的排列组合 return variants.Any(x => EnabledSettings.Contains(x)); } } // 用例 bool featureOneEnabled = new FeatureOne().IsEnabled(Food.Pizza, Drink.Beer, Dessert.IceCream); Console.WriteLine(featureOneEnabled) // 预期匹配pizza_beer_*规则返回true,当前逻辑返回false
目前我有两个疑问:
- 怎么在考虑可选参数的前提下正确生成所有需要匹配的排列组合?
- 这个实现思路是不是最优方案?
补充说明
- 所有匹配用到的属性都是枚举类型
EnabledSettings列表由应用外部传入,不是硬编码在代码里的
实现方案
生成全排列再比对的思路性能很差,一旦枚举值多、维度多,生成的组合数量会指数级上涨,完全没必要。更通用高效的做法是直接把配置项解析成匹配规则,反过来校验入参是否符合规则,不需要生成任何排列组合。
核心逻辑
- 先把外部传入的
EnabledSettings做预解析:按下划线拆分每个配置项,把每个分段存成规则片段,*标记为通配符,同时预校验规则长度和当前特性的字段数量是否一致,过滤掉无效配置。 - 校验入参的时候,把入参也按顺序转成字符串数组,逐个和规则片段比对:只要某条规则的每个片段要么是
*,要么和入参对应位置的字符串完全相等,就判定为匹配命中。
可复用的通用实现代码
// 通用匹配规则类,所有特性共用 public class SettingMatchRule { private readonly string[] _segments; public SettingMatchRule(string configStr) { _segments = configStr.Split('_', StringSplitOptions.RemoveEmptyEntries); } // 校验入参数组是否匹配当前规则 public bool IsMatch(string[] inputValues) { // 长度不一致直接不匹配 if (inputValues.Length != _segments.Length) return false; for (int i = 0; i < _segments.Length; i++) { // 通配符跳过比对,其他片段必须完全相等 if (_segments[i] != "*" && !_segments[i].Equals(inputValues[i], StringComparison.OrdinalIgnoreCase)) { return false; } } return true; } } // 扩展方法做通用逻辑封装 public static class SettingMatchHelper { public static bool CheckEnabled<TEnum1, TEnum2, TEnum3>(this List<string> enabledSettings, TEnum1 first, TEnum2 second, TEnum3 third) where TEnum1 : Enum where TEnum2 : Enum where TEnum3 : Enum { var input = new[] { first.ToString(), second.ToString(), third.ToString() }; // 实际项目可以把解析结果缓存起来避免重复解析 var rules = enabledSettings.Select(s => new SettingMatchRule(s)); return rules.Any(r => r.IsMatch(input)); } public static bool CheckEnabled<TEnum1, TEnum2>(this List<string> enabledSettings, TEnum1 first, TEnum2 second) where TEnum1 : Enum where TEnum2 : Enum { var input = new[] { first.ToString(), second.ToString() }; var rules = enabledSettings.Select(s => new SettingMatchRule(s)); return rules.Any(r => r.IsMatch(input)); } }
改造后的特性类使用示例
public class FeatureOne { public List<string> EnabledSettings = new List<string> { "pizza_milk_iceCream", "pizza_beer_*", "meatballs_water_slushy", "meatballs_*_cookies", "sallad_tea_candy" }; public bool IsEnabled(Food food, Drink drink, Dessert dessert) { return EnabledSettings.CheckEnabled(food, drink, dessert); } } public class FeatureTwo { public List<string> EnabledSettings = new List<string> { "pizza_milk", "meatballs_water", "sallad_tea", "soup_*" }; public bool IsEnabled(Food food, Drink drink) { return EnabledSettings.CheckEnabled(food, drink); } }
方案优势
- 不需要生成任何排列组合,时间复杂度是
O(配置项数量 * 字段维度数),性能比全排列方案高几个量级,哪怕后续加更多枚举、更多维度也不会有性能问题 - 完全兼容之前的非通配符配置,旧逻辑不需要改动
- 支持任意位置的通配符,不管是开头、中间还是结尾的
*都能正确匹配 - 配置完全由外部传入,不需要在代码里硬编码字段是否可选,可选逻辑完全由配置里的
*控制,灵活度更高 - 后续如果要加单维度、四维度的匹配,只要多写一个对应参数数量的泛型扩展方法就行,复用性很强
优化提示:如果
EnabledSettings是固定不变的,可以在程序启动时就把所有配置解析成SettingMatchRule缓存起来,不用每次校验都重新解析,性能还能再提升。
内容的提问来源于stack exchange,提问作者maakusalexander
相关产品推荐
相关产品推荐

