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

如何基于友好名称实现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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.25 07:49:28