前言
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();
- }
- }
复制代码
- 编译运行, 编译器会自动生成罗列形貌扩展方法的代码。
演示程序截图:
总结
通过增量源生成器, 我们可以在编译期自动生成获取罗列形貌的代码, 无需反射, 性能更高。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |