C# - 获取枚举描述 - 使用增量源生成器
前言
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)
创建一个基于 .NET Standard 2.0 的类库项目名为:
SourceGenerator添加 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>
- 添加
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
- 使用 .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>
- 在
MainProject中使用生成的枚举描述扩展方法
namespace MainProject;
class Program
{
static void Main()
{
foreach (var color in Enum.GetValues<Color>())
{
Console.WriteLine(color.GetDescription());
}
Console.ReadKey();
}
}
- 编译运行, 编译器会自动生成枚举描述扩展方法的代码。
演示程序截图:


总结
通过增量源生成器, 我们可以在编译期自动生成获取枚举描述的代码, 无需反射, 性能更高。
C# - 获取枚举描述 - 使用增量源生成器的更多相关文章
- C#获取枚举描述代码
public class MusterEnum { /// 获取枚举的描述信息 /// </summary> /// <param name="e">传入枚 ...
- .NET--------枚举扩展方法(枚举转list,获取枚举描述)
/// <summary> /// get enum description by name /// </summary> /// <typeparam name=&qu ...
- .NET获取枚举DescriptionAttribute描述信息性能改进的多种方法
一. DescriptionAttribute的普通使用方式 1.1 使用示例 DescriptionAttribute特性可以用到很多地方,比较常见的就是枚举,通过获取枚举上定义的描述信息在UI上显 ...
- C#枚举扩展方法,获取枚举值的描述值以及获取一个枚举类下面所有的元素
/// <summary> /// 枚举扩展方法 /// </summary> public static class EnumExtension { private stat ...
- C#通过反射进行枚举描述相关操作
C#可以通过反射,来获取枚举的描述信息或通过描述信息获取到指定类型的枚举 /// <summary> /// 获取枚举描述 /// </summary> /// <par ...
- 【转载】[C#]枚举操作(从枚举中获取Description,根据Description获取枚举,将枚举转换为ArrayList)工具类
关键代码: using System; using System.Collections; using System.Collections.Generic; using System.Compone ...
- C# 读取枚举描述信息实例
using System;using System.Collections;using System.Collections.Generic;using System.Linq;using Syste ...
- c#枚举 获取枚举键值对、描述等
using System; using System.Collections.Generic; using System.Collections.Specialized; using System.C ...
- .net工具类 获取枚举类型的描述
一般情况我们会用枚举类型来存储一些状态信息,而这些信息有时候需要在前端展示,所以需要展示中文注释描述. 为了方便获取这些信息,就封装了一个枚举扩展类. /// <summary> /// ...
- 枚举Enum转换为List,获取枚举的描述
代码: public class EnumberHelper { public static List<EnumberEntity> EnumToList<T>() { Lis ...
随机推荐
- 视频直播技术干货(十二):从入门到放弃,快速学习Android端直播技术
本文由陆业聪分享,原题"一文掌握直播技术:实时音视频采集.编码.传输与播放",本文进行了排版和内容优化. 1.引言 从游戏.教育.电商到娱乐,直播技术的应用场景无处不在.随着移动端 ...
- Mysql连接报错排查解决记录
Mysql连接报错排查解决记录 背景: 系统:uos server-1060e 运行环境kvm虚拟机 mysql版本:5.7.44, for Linux (x86_64) 问题现象: 宿主机重 ...
- Solution -「AGC 020F」Arcs on a Circle
\(\mathscr{Description}\) Link. 在一个周长为 \(c\) 的圆周上放置长度分别为 \(l_1,l_2,\cdots,l_n\) 的弧,每条弧的位置独立均匀随机. ...
- 【Git】---工作区、暂存区、版本库、远程仓库
工作区.暂存区.版本库.远程仓库 一.概念 1.四个工作区域 Git本地有四个工作区域:工作目录(Working Directory).暂存区(Stage/Index).资源库(Repository或 ...
- 多方安全计算(4):MPC万能积木-秘密共享
学习&转载文章:多方安全计算(4):MPC万能积木-秘密共享 前言 在之前的文章(多方安全计算(3)MPC万能钥匙:混淆电路中,我们对MPC中一类通用方案混淆电路(GC)与密文比较策略做了介绍 ...
- HiJobQueue:一个简单的线程安全任务队列
HiJobQueue:一个简单的线程安全任务队列 概述 HiJobQueue 是一个线程安全的任务队列,用于在多线程环境中管理和执行异步任务.它的设计参考了 Cobalt 项目中的 JobQueue, ...
- C# 10个常用特性
感谢一傻小冲的分享 https://www.cnblogs.com/liyichong/p/5434309.html 觉得很实用就搬抄一份收藏,上了年纪记忆力不好了. 1) async / await ...
- 数据存储“取经路”,HBlock轻松“渡”!
近日,天翼云联合权威科技媒体InfoQ举办了以"新存储,更轻量"为主题的线上技术分享会.天翼云存储产品线总监武志民讲解了HBlock的创新设计和技术. 高性能·高可用·高可靠 自研 ...
- Vegeta HTTP 负载测试工具
Go接口压测的第三方包,一个很好用的负载测试工具.vegeta测试工具组件(可执行文件)支持linux以及mac系统,这里指的是通过终端命令行进行进行测试,不需要从代码层面使用这个工具的时候支持lin ...
- 修改NuGet包默认存放位置
默认情况下,NuGet下载的包存放在系统盘(C盘中),这样一来,时间长了下载的包越多,C盘占用的空间也就越多. 1.问题描述 默认情况下,NuGet下载的包存放在系统盘(C盘中,一般在路径C:\Use ...