前言

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 代替缓存。

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. 编译运行, 编译器会自动生成枚举描述扩展方法的代码。

演示程序截图:

总结

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

C# - 获取枚举描述 - 使用增量源生成器的更多相关文章

  1. C#获取枚举描述代码

    public class MusterEnum { /// 获取枚举的描述信息 /// </summary> /// <param name="e">传入枚 ...

  2. .NET--------枚举扩展方法(枚举转list,获取枚举描述)

    /// <summary> /// get enum description by name /// </summary> /// <typeparam name=&qu ...

  3. .NET获取枚举DescriptionAttribute描述信息性能改进的多种方法

    一. DescriptionAttribute的普通使用方式 1.1 使用示例 DescriptionAttribute特性可以用到很多地方,比较常见的就是枚举,通过获取枚举上定义的描述信息在UI上显 ...

  4. C#枚举扩展方法,获取枚举值的描述值以及获取一个枚举类下面所有的元素

    /// <summary> /// 枚举扩展方法 /// </summary> public static class EnumExtension { private stat ...

  5. C#通过反射进行枚举描述相关操作

    C#可以通过反射,来获取枚举的描述信息或通过描述信息获取到指定类型的枚举 /// <summary> /// 获取枚举描述 /// </summary> /// <par ...

  6. 【转载】[C#]枚举操作(从枚举中获取Description,根据Description获取枚举,将枚举转换为ArrayList)工具类

    关键代码: using System; using System.Collections; using System.Collections.Generic; using System.Compone ...

  7. C# 读取枚举描述信息实例

    using System;using System.Collections;using System.Collections.Generic;using System.Linq;using Syste ...

  8. c#枚举 获取枚举键值对、描述等

    using System; using System.Collections.Generic; using System.Collections.Specialized; using System.C ...

  9. .net工具类 获取枚举类型的描述

    一般情况我们会用枚举类型来存储一些状态信息,而这些信息有时候需要在前端展示,所以需要展示中文注释描述. 为了方便获取这些信息,就封装了一个枚举扩展类. /// <summary> /// ...

  10. 枚举Enum转换为List,获取枚举的描述

    代码: public class EnumberHelper { public static List<EnumberEntity> EnumToList<T>() { Lis ...

随机推荐

  1. .Net程序员机会来了,微软官方新推出一个面向Windows开发者本地运行AI模型的开源工具

    想要开发AI产品的.Net程序员机会来了,这个项目应该好好研究. 虽然说大模型基本都有提供网络API,但肯定没有直接使用本地模型速度快. 最近微软官方新推出AI Dev Gallery开源项目,可以帮 ...

  2. Golang基础-字节跳动青训营

    Golang 安装 访问 https://go.dev/ ,点击 Download ,下载对应平台安装包,安装即可 如果无法访问上述网址,可以改为访问 https://studygolang.com/ ...

  3. 我的c语言笔记

    1. 进制转换 二进制.八进制和十六进制向十进制转换都非常容易,就是"按权相加".如:1010.1101 = 1×23 + 0×22 + 1×21 + 0×20 + 1×2-1 + ...

  4. cpa-审计

    1.审计概述 2.审计计划 3.审计证据 4.审计抽样方法 5.信息技术对审计的影响 6.审计工作底稿 7.风险评估 8.风险应对 9.销售与收款循环的审计 10.采购与付款循环的审计 11.生产与存 ...

  5. C# 开发工具Visual Studio 介绍

    Visual Studio Community (社区版) 这个版本的 Visual Studio 是免费的,具备以前 Professional 版的功能.使用时间有许可限制.它对开源项目和培训.学术 ...

  6. 「CF1101F」Trucks and Cities

    题意描述 有 \(N\) 座城市,第 \(i\) 座坐标为 \(a_i\) ,有 \(M\) 辆卡车,第 \(i\) 辆卡车要从城市 \(s_i\) 前往城市 \(e_i\) ,每单位长度耗油量为 \ ...

  7. NOIp 2024 考试策略

    无论简不简单,都要在前 30min 浏览所有题面,思考哪题可做.哪题不可做,思考能打哪些部分分,9:00 再开始写 T1. 题目简单时 9:00 开写后,30min 以内切完 T1. 9:30 开 T ...

  8. RMAN备份时遇到ORA-48132 &ORA-48170且备份变慢案例

    现象描述: 环境: 操作系统:Red Hat Enterprise Linux release 8.10 数据库版本: Oracle 19.24.0.0.0 企业版 备份作业在执行RMAN备份时,告警 ...

  9. 洛谷P2789 直线交点数 题解

    解题思路 考虑将直线分组,每组内直线互相平行,任意两组直线间交点数量等于两组内直线数量乘积. 分组操作使用dfs,求出交点数量后加入set去重,输出set大小. 时间复杂度O(2NN2)有点鬼畜但是可 ...

  10. Jquery常用小操作

    Jq常用操作 jQuery:jQuery 是一个高效.精简并且功能丰富的 JavaScript 工具库 概念1: jQuery对象与Dom对象的区别 jQuery 的对象才可以使用 jQuery 的方 ...