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

C#实现含通配符的下划线分隔配置字符串通用匹配方案

问题背景

假设我有一个字符串列表,列表内的字符串格式分为三类:

  • property1
  • property1_property2
  • property1_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

目前我有两个疑问:

  1. 怎么在考虑可选参数的前提下正确生成所有需要匹配的排列组合?
  2. 这个实现思路是不是最优方案?
补充说明
  • 所有匹配用到的属性都是枚举类型
  • EnabledSettings 列表由应用外部传入,不是硬编码在代码里的

实现方案

生成全排列再比对的思路性能很差,一旦枚举值多、维度多,生成的组合数量会指数级上涨,完全没必要。更通用高效的做法是直接把配置项解析成匹配规则,反过来校验入参是否符合规则,不需要生成任何排列组合。

核心逻辑

  1. 先把外部传入的EnabledSettings做预解析:按下划线拆分每个配置项,把每个分段存成规则片段,*标记为通配符,同时预校验规则长度和当前特性的字段数量是否一致,过滤掉无效配置。
  2. 校验入参的时候,把入参也按顺序转成字符串数组,逐个和规则片段比对:只要某条规则的每个片段要么是*,要么和入参对应位置的字符串完全相等,就判定为匹配命中。

可复用的通用实现代码

// 通用匹配规则类,所有特性共用
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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 10:36:24