如何基于友好名称实现Enum与字符串的双向通用转换?
我太懂这种重复写转换代码的痛苦了!每次新增一个Enum都要复制粘贴IdToName和NameToId的逻辑,既浪费时间又容易出错。这里有几个成熟的通用方案,能让你彻底摆脱这种重复劳动,新增Enum时几乎不用写额外代码:
方案1:特性+反射+缓存的通用工具类(最易实现)
这个方案的核心是用自定义特性标记Enum值的友好名称,然后写一个通用工具类封装所有转换逻辑,完全不需要在每个Enum类里重复写转换方法。
第一步:定义友好名称特性
先写一个特性类,用来给Enum的每个值绑定用户友好的字符串:
[AttributeUsage(AttributeTargets.Field)] public class DisplayTextAttribute : Attribute { public string Text { get; } public DisplayTextAttribute(string text) { Text = text; } }
第二步:编写通用转换工具类
这个类会用反射解析Enum的特性,并且缓存映射关系(避免每次反射影响性能),封装双向转换的逻辑:
public static class EnumDisplayHelper { // 缓存Enum值到友好文本的映射 private static readonly Dictionary<Type, Dictionary<Enum, string>> _enumToTextCache = new(); // 缓存友好文本到Enum值的映射 private static readonly Dictionary<Type, Dictionary<string, Enum>> _textToEnumCache = new(); // Enum转友好文本的扩展方法(直接给Enum值加方法,调用更方便) public static string ToDisplayText<TEnum>(this TEnum enumValue) where TEnum : Enum { var enumType = typeof(TEnum); if (!_enumToTextCache.TryGetValue(enumType, out var map)) { map = BuildEnumToTextMap<TEnum>(); _enumToTextCache[enumType] = map; } // 如果没有找到特性,默认返回Enum的字符串名称 return map.TryGetValue(enumValue, out var text) ? text : enumValue.ToString(); } // 友好文本转Enum值的通用方法 public static TEnum? FromDisplayText<TEnum>(string displayText) where TEnum : Enum { var enumType = typeof(TEnum); if (!_textToEnumCache.TryGetValue(enumType, out var map)) { map = BuildTextToEnumMap<TEnum>(); _textToEnumCache[enumType] = map; } map.TryGetValue(displayText, out var enumValue); return enumValue; } // 构建Enum到文本的映射(私有方法,内部调用) private static Dictionary<Enum, string> BuildEnumToTextMap<TEnum>() where TEnum : Enum { var map = new Dictionary<Enum, string>(); foreach (var field in typeof(TEnum).GetFields(BindingFlags.Public | BindingFlags.Static)) { var enumValue = (Enum)field.GetValue(null)!; var displayAttr = field.GetCustomAttribute<DisplayTextAttribute>(); map[enumValue] = displayAttr?.Text ?? enumValue.ToString(); } return map; } // 构建文本到Enum的映射(私有方法,内部调用) private static Dictionary<string, Enum> BuildTextToEnumMap<TEnum>() where TEnum : Enum { // 这里用忽略大小写的比较器,适配用户输入的大小写差异,可根据需求调整 var map = new Dictionary<string, Enum>(StringComparer.OrdinalIgnoreCase); foreach (var field in typeof(TEnum).GetFields(BindingFlags.Public | BindingFlags.Static)) { var enumValue = (Enum)field.GetValue(null)!; var displayAttr = field.GetCustomAttribute<DisplayTextAttribute>(); var text = displayAttr?.Text ?? enumValue.ToString(); map[text] = enumValue; } return map; } }
第三步:使用方式(零重复代码)
定义Enum时只需要给每个值标记特性即可,完全不需要写任何转换代码:
public enum OrderStatus { [DisplayText("待支付")] PendingPayment, [DisplayText("已支付")] Paid, [DisplayText("已发货")] Shipped, [DisplayText("已完成")] Completed }
调用转换逻辑的时候直接用扩展方法或通用方法:
// Enum转友好文本 var status = OrderStatus.Paid; var displayText = status.ToDisplayText(); // 输出:"已支付" // 友好文本转Enum var statusFromText = EnumDisplayHelper.FromDisplayText<OrderStatus>("已发货"); // 输出:OrderStatus.Shipped
方案2:源生成器(编译时生成代码,性能最优)
如果你的项目用的是.NET 5+,可以用源生成器在编译时自动为Enum生成转换代码,完全避免反射的性能开销,而且新增Enum时只要加特性,自动生成转换方法,彻底零手动代码。
核心思路
源生成器会在编译时扫描所有带DisplayTextAttribute的Enum,自动生成对应的ToDisplayText和FromDisplayText扩展方法,比如上面的OrderStatus会生成:
public static class OrderStatusDisplayExtensions { public static string ToDisplayText(this OrderStatus enumValue) { return enumValue switch { OrderStatus.PendingPayment => "待支付", OrderStatus.Paid => "已支付", OrderStatus.Shipped => "已发货", OrderStatus.Completed => "已完成", _ => enumValue.ToString() }; } public static OrderStatus? FromDisplayText(string displayText) { return displayText switch { "待支付" => OrderStatus.PendingPayment, "已支付" => OrderStatus.Paid, "已发货" => OrderStatus.Shipped, "已完成" => OrderStatus.Completed, _ => default }; } }
简单实现示例
源生成器的代码需要单独放在一个类库项目里,这里给个简化版的实现:
using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using System.Text; [Generator] public class EnumDisplayGenerator : ISourceGenerator { public void Initialize(GeneratorInitializationContext context) { // 注册语法接收器,监听Enum定义 context.RegisterForSyntaxNotifications(() => new EnumSyntaxReceiver()); } public void Execute(GeneratorExecutionContext context) { var receiver = (EnumSyntaxReceiver)context.SyntaxReceiver!; foreach (var enumDeclaration in receiver.EnumDeclarations) { var semanticModel = context.Compilation.GetSemanticModel(enumDeclaration.SyntaxTree); var enumSymbol = semanticModel.GetDeclaredSymbol(enumDeclaration) as INamedTypeSymbol; if (enumSymbol == null) continue; // 生成扩展方法代码 var sourceCode = GenerateDisplayExtensionCode(enumSymbol); context.AddSource($"{enumSymbol.Name}DisplayExtensions.g.cs", sourceCode); } } private string GenerateDisplayExtensionCode(INamedTypeSymbol enumSymbol) { var enumName = enumSymbol.Name; var namespaceName = enumSymbol.ContainingNamespace.ToDisplayString(); var stringBuilder = new StringBuilder(); // 生成命名空间和类 stringBuilder.Append($@" namespace {namespaceName} {{ public static class {enumName}DisplayExtensions {{ public static string ToDisplayText(this {enumName} enumValue) {{ return enumValue switch {{ "); // 生成每个Enum值的分支 foreach (var member in enumSymbol.GetMembers().OfType<IFieldSymbol>().Where(m => m.IsStatic)) { var displayAttr = member.GetAttributes().FirstOrDefault(a => a.AttributeClass?.Name == "DisplayTextAttribute"); var text = displayAttr?.ConstructorArguments.FirstOrDefault().Value?.ToString() ?? member.Name; stringBuilder.AppendLine($" {enumName}.{member.Name} => \"{text}\","); } // 完成ToDisplayText方法 stringBuilder.Append($@" _ => enumValue.ToString() }}; }} public static {enumName}? FromDisplayText(string displayText) {{ return displayText switch {{ "); // 生成FromDisplayText的分支 foreach (var member in enumSymbol.GetMembers().OfType<IFieldSymbol>().Where(m => m.IsStatic)) { var displayAttr = member.GetAttributes().FirstOrDefault(a => a.AttributeClass?.Name == "DisplayTextAttribute"); var text = displayAttr?.ConstructorArguments.FirstOrDefault().Value?.ToString() ?? member.Name; stringBuilder.AppendLine($" \"{text}\" => {enumName}.{member.Name},"); } // 完成FromDisplayText方法和类 stringBuilder.Append($@" _ => default }}; }} }} }}"); return stringBuilder.ToString(); } // 语法接收器,用来收集所有Enum定义 private class EnumSyntaxReceiver : ISyntaxReceiver { public List<EnumDeclarationSyntax> EnumDeclarations { get; } = new(); public void OnVisitSyntaxNode(SyntaxNode syntaxNode) { if (syntaxNode is EnumDeclarationSyntax enumDeclaration) { // 只收集带有DisplayTextAttribute的Enum,可根据需求调整 if (enumDeclaration.Members.Any(m => m.AttributeLists.Any(a => a.Attributes.Any(attr => attr.Name.ToString() == "DisplayText")))) { EnumDeclarations.Add(enumDeclaration); } } } } }
额外优化建议
- 多语言支持:可以把
DisplayTextAttribute改成支持资源键,比如[DisplayText("OrderStatus_PendingPayment")],然后在转换时从资源文件读取对应语言的文本。 - 容错处理:在
FromDisplayText方法里可以添加对Enum字符串名称的兼容,比如用户输入"PendingPayment"也能转换为OrderStatus.PendingPayment。 - 缓存预热:如果有常用的Enum,可以在应用启动时提前调用一次转换方法,把缓存加载好。
内容的提问来源于stack exchange,提问作者Eternal21
相关产品推荐
相关产品推荐

