吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 303|回复: 2
收起左侧

[其他原创] C# - 获取枚举描述 - 使用增量源生成器

[复制链接]
Broadm 发表于 2025-3-24 14:39

前言

C# 获取枚举描述的方法有很多, 常用的有通过 DescriptionAttribute 反射获取, 进阶的可以加上缓存机制, 减少反射的开销。今天我们还提供一种更加高效的方法,通过增量源生成器生成获取枚举描述的代码。这是在编译层面实现的, 无需反射, 性能更高。

本文的演示代码基于 VS2022 + .NET 8.0 + .NET Standard 2.0

1. 基本反射

这种方法是最常用的方法, 但是反射开销比较大。

public enum Color
{
    [Description("红色")]
    Red,
    [Description("绿色")]
    Green,
    [Description("蓝色")]
    Blue
}

public static string GetDescription(Color color)
{
    var fieldInfo = typeof(Color).GetField(color.ToString());
    var descriptionAttribute = fieldInfo.GetCustomAttribute<DescriptionAttribute>();
    return descriptionAttribute?.Description;
}

2. 反射 + 缓存

缓存机制可以减少反射的开销, 避免反射过于频繁。

private static readonly Dictionary<Color, string> _descriptionCache = new Dictionary<Color, string>();

public static string GetDescription(Color color)
{
    if (_descriptionCache.TryGetValue(color, out var description))
    {
        return description;
    }

    var fieldInfo = typeof(Color).GetField(color.ToString());
    var descriptionAttribute = fieldInfo.GetCustomAttribute<DescriptionAttribute>();
    description = descriptionAttribute?.Description;
    _descriptionCache.Add(color, description);
    return description;
}

3. 反射 + 缓存 + 泛型类 (推荐)

泛型可以减少代码重复。下面的代码为基本实现, 没有考虑线程安全问题。线程安全问题可以通过锁机制解决。可以使用静态构造函数初始化缓存。或者使用 ConcurrentDictionary 代替 Dictionary。或者使用 Lazy<T> 代替缓存。

public class EnumDescription<T> where T : Enum
{
    private static readonly Dictionary<T, string> _descriptionCache = new Dictionary<T, string>();

    public static string GetDescription(T value)
    {
        if (_descriptionCache.TryGetValue(value, out var description))
        {
            return description;
        }

        var fieldInfo = typeof(T).GetField(value.ToString());
        var descriptionAttribute = fieldInfo.GetCustomAttribute<DescriptionAttribute>();
        description = descriptionAttribute?.Description;
        _descriptionCache.Add(value, description);
        return description;
    }
}

4. 增量源生成器 (消除反射)

创建增量源生成器类库项目 (.NET Standard 2.0)

  1. 创建一个基于 .NET Standard 2.0 的类库项目名为: SourceGenerator

  2. 添加 NuGet 包 Microsoft.CodeAnalysis.CSharp 版本 4.8.0

<Project Sdk="Microsoft.NET.Sdk">
    <PropertyGroup>
        <TargetFramework>netstandard2.0</TargetFramework>
        <LangVersion>latest</LangVersion>
        <EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
    </PropertyGroup>

    <ItemGroup>
        <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.8.0" />
    </ItemGroup>
</Project>
  1. 添加 EnumDescriptionGenerator 类, 实现 IIncrementalGenerator 接口
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;

[Generator]
public class EnumDescriptionGenerator : IIncrementalGenerator
{
    public void Initialize(IncrementalGeneratorInitializationContext context)
    {
        var enumDeclarations = context.SyntaxProvider
           .CreateSyntaxProvider(
                predicate: (syntaxNode, _) => syntaxNode is EnumDeclarationSyntax,
                transform: (generatorSyntaxContext, _) =>
                {
                    var enumDeclaration = (EnumDeclarationSyntax)generatorSyntaxContext.Node;
                    var enumSymbol = generatorSyntaxContext.SemanticModel.GetDeclaredSymbol(enumDeclaration) as INamedTypeSymbol;
                    return new { EnumDeclaration = enumDeclaration, EnumSymbol = enumSymbol };
                })
           .Where(t => t.EnumSymbol != null)
           .Collect();

        var compilationAndEnums = context.CompilationProvider.Combine(enumDeclarations);

        context.RegisterSourceOutput(compilationAndEnums, (sourceProductionContext, tuple) =>
        {
            var compilation = tuple.Left;
            var enums = tuple.Right;

            foreach (var item in enums)
            {
                var enumDeclaration = item.EnumDeclaration;
                var enumSymbol = item.EnumSymbol;

                if (!enumSymbol.GetMembers("GetDescription").Any())
                {
                    var source = GenerateSourceCode(enumSymbol);
                    sourceProductionContext.AddSource($"{enumSymbol.Name}Descriptions.g.cs", SourceText.From(source, Encoding.UTF8));
                }
            }

        });
    }

    // 生成枚举描述扩展方法的代码
    private static string GenerateSourceCode(INamedTypeSymbol enumSymbol)
    {
        var enumName = enumSymbol.Name;
        var namespaceName = enumSymbol.ContainingNamespace?.ToString() ?? "Global";

        var sb = new StringBuilder();
        sb.AppendLine($"namespace {namespaceName};");
        sb.AppendLine($"public static partial class {enumName}Extensions");
        sb.AppendLine("{");
        sb.AppendLine($"    public static string GetDescription(this {enumName} value) =>");
        sb.AppendLine("        value switch");
        sb.AppendLine("        {");

        // 4. 遍历枚举成员
        foreach (var member in enumSymbol.GetMembers().Where(m => m.Kind == SymbolKind.Field))
        {
            var description = member.GetAttributes()
                .FirstOrDefault(a => a.AttributeClass?.Name == "DescriptionAttribute")
                ?.ConstructorArguments.FirstOrDefault().Value?.ToString()
                ?? member.Name;

            sb.AppendLine($"            {enumName}.{member.Name} => \"{description}\",");
        }

        sb.AppendLine("            _ => string.Empty");
        sb.AppendLine("        };");
        sb.AppendLine("}");
        return sb.ToString();
    }
}

创建控制台主项目 MainProject

  1. 使用 .NET 8.0 , 引用 SourceGenerator 项目, 注意引用方式如下:
<Project Sdk="Microsoft.NET.Sdk">

    <PropertyGroup>
        <OutputType>Exe</OutputType>
        <TargetFramework>net8.0</TargetFramework>
        <ImplicitUsings>enable</ImplicitUsings>
        <Nullable>enable</Nullable>
    </PropertyGroup>

    <ItemGroup>
        <ProjectReference Include="..\SourceGenerator\SourceGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
    </ItemGroup>

</Project>
  1. MainProject 中使用生成的枚举描述扩展方法
namespace MainProject;

class Program
{
    static void Main()
    {
        foreach (var color in Enum.GetValues<Color>())
        {
            Console.WriteLine(color.GetDescription());
        }
        Console.ReadKey();
    }
}
  1. 编译运行, 编译器会自动生成枚举描述扩展方法的代码。

演示程序截图:

776421-20250322163840591-441405763.png
776421-20250322164350209-1779171688.png

总结

通过增量源生成器, 我们可以在编译期自动生成获取枚举描述的代码, 无需反射, 性能更高。

免费评分

参与人数 2吾爱币 +7 热心值 +2 收起 理由
苏紫方璇 + 7 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
Xinchen86 + 1 谢谢楼主分享

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

Xinchen86 发表于 2025-3-26 16:40
谢谢楼主分享
 楼主| Broadm 发表于 2025-3-27 09:41
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2025-4-8 09:36

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表