C#枚举匹配是否有更优雅高性能的非遍历实现方案?
C# 多公司枚举映射优雅实现方案
你现在代码冗余的核心原因是把员工-公司、公司-行为值的绑定逻辑硬编码在了分支判断里,每加一家公司就要复制粘贴一段遍历代码,维护成本随公司/员工数量线性上涨。下面两个方案都完全离线运行,不需要数据库或外部依赖,扩展成本极低。
方案1:反射自动扫描映射(零硬编码,扩展最方便)
核心逻辑是给相关枚举加统一标记,程序启动时自动扫描所有枚举构建内存查找表,后续查询直接走字典O(1)返回结果,新增公司/员工时不需要修改核心查找逻辑。
- 首先定义两个标记特性,用来区分员工枚举和行为枚举:
[AttributeUsage(AttributeTargets.Enum)] internal class CompanyEmployeeEnumAttribute : Attribute { } [AttributeUsage(AttributeTargets.Enum)] internal class CompanyActionEnumAttribute : Attribute { } - 给原有枚举加上对应标记:
[CompanyEmployeeEnum] internal enum CompanyA { workerA = 21, workerB = 42, } [CompanyEmployeeEnum] internal enum CompanyB { workerC = 22, workerD = 44, } [CompanyActionEnum] internal enum CompanyA_Do { DoA = 11, DoB = 5, } [CompanyActionEnum] internal enum CompanyB_Do { DoA = 15, DoB = 55, } - 写静态解析类,程序启动时一次性加载所有映射关系到内存:
internal static class CompanyConfigResolver { // 员工名 -> 所属公司枚举类型 映射 private static readonly IReadOnlyDictionary<string, Type> _employeeToCompanyMap; // 公司枚举类型 -> (行为名, 行为值) 映射 private static readonly IReadOnlyDictionary<Type, IReadOnlyDictionary<string, int>> _companyActionMap; static CompanyConfigResolver() { // 扫描当前程序集所有标记过的枚举 var allMarkedEnums = AppDomain.CurrentDomain.GetAssemblies() .SelectMany(asm => asm.GetTypes()) .Where(t => t.IsEnum) .Where(t => t.GetCustomAttributes(inherit: false) .Any(attr => attr is CompanyEmployeeEnumAttribute or CompanyActionEnumAttribute)) .ToList(); var employeeEnums = allMarkedEnums.Where(t => t.GetCustomAttributes<CompanyEmployeeEnumAttribute>(false).Any()).ToList(); var actionEnums = allMarkedEnums.Where(t => t.GetCustomAttributes<CompanyActionEnumAttribute>(false).Any()).ToList(); // 构建员工所属公司映射 var empMap = new Dictionary<string, Type>(StringComparer.OrdinalIgnoreCase); foreach (var empEnumType in employeeEnums) { foreach (var empName in Enum.GetNames(empEnumType)) { empMap[empName] = empEnumType; } } _employeeToCompanyMap = empMap; // 构建公司行为值映射 var actionMap = new Dictionary<Type, IReadOnlyDictionary<string, int>>(); foreach (var actionEnum in actionEnums) { // 行为枚举名去掉末尾的"_Do"后缀,就是对应员工枚举的类名 string matchedCompanyEnumName = actionEnum.Name[..^3]; var matchedCompanyEnum = employeeEnums.First(t => t.Name == matchedCompanyEnumName); var actionValueMap = Enum.GetValues(actionEnum) .Cast<object>() .ToDictionary( keySelector: val => Enum.GetName(actionEnum, val)!, elementSelector: val => (int)val ); actionMap[matchedCompanyEnum] = actionValueMap; } _companyActionMap = actionMap; } /// <summary> /// 获取当前登录用户对应的行为配置值 /// </summary> public static bool TryGetUserActionValue(bool isDoA, bool isDoB, out int workerDoValue, out bool isCompanyA) { workerDoValue = default; isCompanyA = default; string currentUser = Environment.UserName; // 未匹配到员工直接返回失败 if (!_employeeToCompanyMap.TryGetValue(currentUser, out var companyType)) return false; isCompanyA = companyType == typeof(CompanyA); string targetActionName = isDoA ? "DoA" : isDoB ? "DoB" : string.Empty; if (string.IsNullOrEmpty(targetActionName)) return false; return _companyActionMap[companyType].TryGetValue(targetActionName, out workerDoValue); } } - 调用方式非常简单,不需要写任何遍历或分支判断公司:
if (CompanyConfigResolver.TryGetUserActionValue(isDoA, isDoB, out int doValue, out bool isCompanyA)) { // doValue就是需要的CompanyX_Do对应int值,isCompanyA是所属公司标记 // 直接使用即可 } else { // 处理当前用户不在员工列表的异常逻辑 }
后续新增公司时,只需要新增CompanyX员工枚举和CompanyX_Do行为枚举,加上对应标记即可,不需要修改任何解析逻辑。
方案2:集中字典映射(无反射,更轻量)
如果不想引入反射,可以直接把所有映射关系集中在一个静态字典里初始化,避免散落在多个遍历分支中:
internal static class SimpleCompanyConfig { // 集中存储所有员工对应的行为值,同公司员工配置相同可以直接复用 private static readonly IReadOnlyDictionary<string, (int DoA, int DoB)> _userConfig = new Dictionary<string, (int, int)>() { { nameof(CompanyA.workerA), ((int)CompanyA_Do.DoA, (int)CompanyA_Do.DoB) }, { nameof(CompanyA.workerB), ((int)CompanyA_Do.DoA, (int)CompanyA_Do.DoB) }, { nameof(CompanyB.workerC), ((int)CompanyB_Do.DoA, (int)CompanyB_Do.DoB) }, { nameof(CompanyB.workerD), ((int)CompanyB_Do.DoA, (int)CompanyB_Do.DoB) }, }; public static bool TryGetCurrentUserValue(bool isDoA, bool isDoB, out int workerDoValue, out bool isCompanyA) { workerDoValue = default; isCompanyA = default; string currentUser = Environment.UserName; if (!_userConfig.TryGetValue(currentUser, out var config)) return false; isCompanyA = currentUser is nameof(CompanyA.workerA) or nameof(CompanyA.workerB); workerDoValue = isDoA ? config.DoA : isDoB ? config.DoB : default; return workerDoValue != default; } }
这个方案性能更高,新增员工/公司时只需要在字典里加一行配置即可,维护成本远低于原来的多段遍历写法。
方案对比
- 方案1适合公司/员工规模较大、后续扩展频繁的场景,完全不需要手动维护映射关系,新增枚举即可自动加载
- 方案2适合规模较小、不想用反射的场景,代码更直白,调试方便
- 两个方案都是启动时一次性加载配置,运行时查询性能都是O(1),完全离线,不需要任何外部存储支持
内容的提问来源于stack exchange,提问作者robni
相关产品推荐
相关产品推荐

