بررسی Source Generators در #C - قسمت سوم - بهبود کارآیی برنامه با تبدیل عملیات Reflection به تولید کد خودکار
نویسنده: وحید نصیری
تاریخ: ۱۴۰۱/۰۴/۲۹ ۱۰:۴۰
آدرس: www.dntips.ir
| مطالب | ۳۶۹۴ |
| نویسندگان | ۲۷۶ |
| گروههای مطالب | ۱۰۲۴ |
| نقشههای راه | ۱۱۹ |
| دورهها | ۱۴ |
| اشتراکها | ۱۷۹۱۴ |
using System.ComponentModel.DataAnnotations;
namespace NotifyPropertyChangedGenerator.Demo;
public enum Gender
{
NotSpecified,
[Display(Name = "مرد")] Male,
[Display(Name = "زن")] Female
} public static class Extensions
{
public static string GetDisplayName(this Enum value)
{
if (value is null)
throw new ArgumentNullException(nameof(value));
var attribute = value.GetType().GetField(value.ToString())?
.GetCustomAttributes<DisplayAttribute>(false).FirstOrDefault();
if (attribute is null)
return value.ToString();
return attribute.GetType().GetProperty("Name")?.GetValue(attribute, null)?.ToString();
}
} namespace NotifyPropertyChangedGenerator.Demo
{
public static class GenderExtensions
{
public static string GetDisplayName(this Gender @enum)
{
return @enum switch
{
Gender.NotSpecified => "NotSpecified",
Gender.Male => "مرد",
Gender.Female => "زن",
_ => throw new ArgumentOutOfRangeException(nameof(@enum))
};
}
}
} [Generator]
public class EnumExtensionsGenerator : ISourceGenerator
{
public void Initialize(GeneratorInitializationContext context)
{}
public void Execute(GeneratorExecutionContext context)
{
var compilation = context.Compilation;
foreach (var syntaxTree in compilation.SyntaxTrees)
{
var semanticModel = compilation.GetSemanticModel(syntaxTree);
var immutableHashSet = syntaxTree.GetRoot()
.DescendantNodesAndSelf()
.OfType<EnumDeclarationSyntax>()
.Select(enumDeclarationSyntax => semanticModel.GetDeclaredSymbol(enumDeclarationSyntax))
.OfType<ITypeSymbol>()
/*.Where(typeSymbol => typeSymbol.GetAttributes().Any(
attributeData => string.Equals(attributeData.AttributeClass?.Name, "GenerateExtensions",
StringComparison.Ordinal)
))*/
.ToImmutableHashSet();
foreach (var typeSymbol in immutableHashSet)
{
var source = GenerateEnumExtensions(typeSymbol);
context.AddSource($"{typeSymbol.Name}Extensions.cs", source);
}
}
} private string GenerateEnumExtensions(ITypeSymbol typeSymbol)
{
return $@"namespace {typeSymbol.ContainingNamespace}
{{
public static class {typeSymbol.Name}Extensions
{{
public static string GetDisplayName(this {typeSymbol.Name} @enum)
{{
{GenerateExtensionMethodBody(typeSymbol)}
}}
}}
}}";
}
private static string GenerateExtensionMethodBody(ITypeSymbol typeSymbol)
{
var sb = new StringBuilder();
sb.Append(@"return @enum switch
{
");
foreach (var fieldSymbol in typeSymbol.GetMembers().OfType<IFieldSymbol>())
{
var displayAttribute = fieldSymbol.GetAttributes()
.FirstOrDefault(attributeData =>
string.Equals(attributeData.AttributeClass?.Name, "DisplayAttribute", StringComparison.Ordinal));
if (displayAttribute is null)
{
sb.AppendLine(
$@" {typeSymbol.Name}.{fieldSymbol.Name} => ""{fieldSymbol.Name}"",");
}
else
{
var displayAttributeName = displayAttribute.NamedArguments
.FirstOrDefault(x => string.Equals(x.Key, "Name", StringComparison.Ordinal))
.Value;
sb.AppendLine(
$@" {typeSymbol.Name}.{fieldSymbol.Name} => ""{displayAttributeName.Value}"",");
}
}
sb.Append(
@" _ => throw new ArgumentOutOfRangeException(nameof(@enum))
};");
return sb.ToString();
} <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
<CompilerGeneratedFilesOutputPath>Generated</CompilerGeneratedFilesOutputPath>
</PropertyGroup>
<Target Name="CleanSourceGeneratedFiles" BeforeTargets="BeforeBuild" DependsOnTargets="$(BeforeBuildDependsOn)">
<RemoveDir Directories="$(CompilerGeneratedFilesOutputPath)" />
</Target>
<ItemGroup>
<!-- Exclude the output of source generators from the compilation -->
<Compile Remove="$(CompilerGeneratedFilesOutputPath)/**/*.cs" />
<Content Include="$(CompilerGeneratedFilesOutputPath)/**" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\NotifyPropertyChangedGenerator\NotifyPropertyChangedGenerator.csproj"
OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
</ItemGroup>
</Project> {BaseIntermediateOutpath}/generated/{Assembly}/{SourceGeneratorName}/{GeneratedFile} <PropertyGroup> <TargetFramework>netstandard2.0</TargetFramework> <IsRoslynComponent>true</IsRoslynComponent>
#if DEBUG
if (!Debugger.IsAttached)
{
Debugger.Launch();
}
#endif