恋恋北风 發表於 2025-3-24 14:26:00

C# - 获取枚举描述 - 使用增量源生成器

<h1 id="前言">前言</h1>
<blockquote>
<p>C# 获取枚举描述的方法有很多, 常用的有通过 <code>DescriptionAttribute</code> 反射获取, 进阶的可以加上缓存机制, 减少反射的开销。今天我们还提供一种更加高效的方法,通过增量源生成器生成获取枚举描述的代码。这是在编译层面实现的, 无需反射, 性能更高。</p>
</blockquote>
<blockquote>
<p>本文的演示代码基于 VS2022 + .NET 8.0 + .NET Standard 2.0</p>
</blockquote>
<h1 id="1-基本反射">1. 基本反射</h1>
<blockquote>
<p>这种方法是最常用的方法, 但是反射开销比较大。</p>
</blockquote>
<pre><code class="language-csharp">public enum Color
{
   
    Red,
   
    Green,
   
    Blue
}

public static string GetDescription(Color color)
{
    var fieldInfo = typeof(Color).GetField(color.ToString());
    var descriptionAttribute = fieldInfo.GetCustomAttribute&lt;DescriptionAttribute&gt;();
    return descriptionAttribute?.Description;
}
</code></pre>
<h1 id="2-反射--缓存">2. 反射 + 缓存</h1>
<blockquote>
<p>缓存机制可以减少反射的开销, 避免反射过于频繁。</p>
</blockquote>
<pre><code class="language-csharp">private static readonly Dictionary&lt;Color, string&gt; _descriptionCache = new Dictionary&lt;Color, string&gt;();

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&lt;DescriptionAttribute&gt;();
    description = descriptionAttribute?.Description;
    _descriptionCache.Add(color, description);
    return description;
}
</code></pre>
<h1 id="3-反射--缓存--泛型类-推荐">3. 反射 + 缓存 + 泛型类 (推荐)</h1>
<blockquote>
<p>泛型可以减少代码重复。下面的代码为基本实现, 没有考虑线程安全问题。线程安全问题可以通过锁机制解决。可以使用静态构造函数初始化缓存。或者使用 ConcurrentDictionary 代替 Dictionary。或者使用 Lazy<t> 代替缓存。</t></p>
</blockquote>
<pre><code class="language-csharp">public class EnumDescription&lt;T&gt; where T : Enum
{
    private static readonly Dictionary&lt;T, string&gt; _descriptionCache = new Dictionary&lt;T, string&gt;();

    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&lt;DescriptionAttribute&gt;();
      description = descriptionAttribute?.Description;
      _descriptionCache.Add(value, description);
      return description;
    }
}
</code></pre>
<h1 id="4-增量源生成器-消除反射">4. 增量源生成器 (消除反射)</h1>
<h3 id="创建增量源生成器类库项目-net-standard-20">创建增量源生成器类库项目 (.NET Standard 2.0)</h3>
<ol>
<li>
<p>创建一个基于 .NET Standard 2.0 的类库项目名为: <code>SourceGenerator</code></p>
</li>
<li>
<p>添加 NuGet 包 <code>Microsoft.CodeAnalysis.CSharp</code> 版本 <code>4.8.0</code></p>
</li>
</ol>
<pre><code class="language-csharp">&lt;Project Sdk="Microsoft.NET.Sdk"&gt;
        &lt;PropertyGroup&gt;
                &lt;TargetFramework&gt;netstandard2.0&lt;/TargetFramework&gt;
                &lt;LangVersion&gt;latest&lt;/LangVersion&gt;
                &lt;EnforceExtendedAnalyzerRules&gt;true&lt;/EnforceExtendedAnalyzerRules&gt;
        &lt;/PropertyGroup&gt;

        &lt;ItemGroup&gt;
                &lt;PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.8.0" /&gt;
        &lt;/ItemGroup&gt;
&lt;/Project&gt;
</code></pre>
<ol start="3">
<li>添加 <code>EnumDescriptionGenerator</code> 类, 实现 <code>IIncrementalGenerator</code> 接口</li>
</ol>
<pre><code class="language-csharp">using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;



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

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

      context.RegisterSourceOutput(compilationAndEnums, (sourceProductionContext, tuple) =&gt;
      {
            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) =&gt;");
      sb.AppendLine("      value switch");
      sb.AppendLine("      {");

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

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

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

</code></pre>
<h3 id="创建控制台主项目-mainproject">创建控制台主项目 <code>MainProject</code></h3>
<ol>
<li>使用 .NET 8.0 , 引用 <code>SourceGenerator</code> 项目, 注意引用方式如下:</li>
</ol>
<pre><code class="language-csharp">&lt;Project Sdk="Microsoft.NET.Sdk"&gt;

        &lt;PropertyGroup&gt;
                &lt;OutputType&gt;Exe&lt;/OutputType&gt;
                &lt;TargetFramework&gt;net8.0&lt;/TargetFramework&gt;
                &lt;ImplicitUsings&gt;enable&lt;/ImplicitUsings&gt;
                &lt;Nullable&gt;enable&lt;/Nullable&gt;
        &lt;/PropertyGroup&gt;

        &lt;ItemGroup&gt;
                &lt;ProjectReference Include="..\SourceGenerator\SourceGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" /&gt;
        &lt;/ItemGroup&gt;

&lt;/Project&gt;

</code></pre>
<ol start="2">
<li>在 <code>MainProject</code> 中使用生成的枚举描述扩展方法</li>
</ol>
<pre><code class="language-csharp">namespace MainProject;

class Program
{
    static void Main()
    {
      foreach (var color in Enum.GetValues&lt;Color&gt;())
      {
            Console.WriteLine(color.GetDescription());
      }
      Console.ReadKey();
    }
}
</code></pre>
<ol start="3">
<li>编译运行, 编译器会自动生成枚举描述扩展方法的代码。</li>
</ol>
<h1 id="演示程序截图">演示程序截图:</h1>
<p><img src="https://img2024.cnblogs.com/blog/776421/202503/776421-20250322163840591-441405763.png" alt="image" loading="lazy"></p>
<p><img src="https://img2024.cnblogs.com/blog/776421/202503/776421-20250322164350209-1779171688.png" alt="image" loading="lazy"></p>
<h1 id="总结">总结</h1>
<p>通过增量源生成器, 我们可以在编译期自动生成获取枚举描述的代码, 无需反射, 性能更高。</p><br><br>
来源:https://www.cnblogs.com/broadm/p/18786843
頁: [1]
查看完整版本: C# - 获取枚举描述 - 使用增量源生成器