gpt4 book ai didi

c# - 用源发生器代替反射

转载 作者:行者123 更新时间:2023-12-05 09:34:14 27 4
gpt4 key购买 nike

我有一个使用反射的工厂,我想将其替换为源生成器生成的工厂。

生成的代码应该是这样的:

using System;

namespace Generated
{
public static class InsuranceFactory
{
public static IInsurance Get(string insuranceName)
{
switch (insuranceName)
{
case "LifeInsurance":
return new Namespace.LifeInsurance();
case "AutoInsurance":
return new AnotherNamespace.AutoInsurance();
default:
throw new Exception($"Insurance not found for name '{insuranceName}'.");
}
}
}
}

使用反射,我发现我的类型是这样的:

List<Type> insuranceTypes = new List<Type>();
Type baseInsuranceType = typeof(IInsurance);
IEnumerable<Assembly> assemblies = AppDomain.CurrentDomain.GetAssemblies().Where(o => !IsFrameworkAssembly(o.FullName ?? String.Empty));

foreach (System.Reflection.Assembly a in assemblies)
{
Type[] types = a.GetTypes();
insuranceTypes.AddRange(types.Where(t => baseInsuranceType.IsAssignableFrom(t) && t.IsClass && !t.IsAbstract && t.Name.StartsWith(prefix) && t.Name.EndsWith(suffix)));
}

如何通过 GeneratorExecutionContext.Compilation 对象进行与通过反射代码相同的搜索?

最佳答案

您必须使用编译器通过执行上下文提供的等效 API。然后根据您希望如何生成源代码,您可以直接生成源文本或生成表示源代码的语法节点。

您需要在Compilation 中深入挖掘以找到实现您的接口(interface)的类型,然后为每个类型生成案例。

这是您可以尝试的一种实现方式:(我无法测试生成器本身,但内容生成应该可以)

[Generator]
public class InsuranceFactoryGenerator : ISourceGenerator
{
const string FactoryNamespaceName = "MyNamespace";
const string QualifiedInterfaceName = "InsuranceCompany.IInsurance";

public void Execute(GeneratorExecutionContext context)
{
var insuranceTypes = GetInsuranceTypes(context.Compilation, context.CancellationToken);
var factoryClass = GenerateFactoryClass(context.Compilation, insuranceTypes, context.CancellationToken);
var factoryContent = NamespaceDeclaration(ParseName(FactoryNamespaceName))
.WithMembers(SingletonList<MemberDeclarationSyntax>(factoryClass));
context.AddSource("InsuranceFactory", factoryContent.NormalizeWhitespace().ToFullString());
}

private IEnumerable<ITypeSymbol> GetInsuranceTypes(Compilation compilation, CancellationToken cancellationToken)
{
var type = compilation.GetTypeByMetadataName(QualifiedInterfaceName)
?? throw new Exception($"Interface '{QualifiedInterfaceName}' not found in compilation");
var classDecls = compilation.SyntaxTrees
.SelectMany(t => t.GetRoot(cancellationToken).DescendantNodes())
.OfType<ClassDeclarationSyntax>();
foreach (var classDecl in classDecls)
{
var classSymbol = GetInsuranceClassSymbol(compilation, type, classDecl, cancellationToken);
if (classSymbol != null)
yield return classSymbol;
}
}

private ITypeSymbol? GetInsuranceClassSymbol(Compilation compilation, ITypeSymbol insuranceSymbol, ClassDeclarationSyntax classDeclaration, CancellationToken cancellationToken)
{
if (classDeclaration.BaseList == null) return null;
var semanticModel = compilation.GetSemanticModel(classDeclaration.SyntaxTree);
foreach (var baseType in classDeclaration.BaseList.Types)
{
var typeSymbol = compilation.GetTypeByMetadataName(baseType.Type.ToString())!;
var conversion = compilation.ClassifyConversion(typeSymbol, insuranceSymbol);
if (conversion.Exists && conversion.IsImplicit)
return semanticModel.GetDeclaredSymbol(classDeclaration, cancellationToken);
}
return null;
}

private ClassDeclarationSyntax GenerateFactoryClass(Compilation compilation, IEnumerable<ITypeSymbol> insuranceTypes, CancellationToken cancellationToken)
{
var paramName = "insuranceName";
return ClassDeclaration("InsuranceFactory")
.WithModifiers(TokenList(Token(SyntaxKind.PublicKeyword), Token(SyntaxKind.StaticKeyword)))
.WithMembers(
SingletonList<MemberDeclarationSyntax>(
MethodDeclaration(ParseTypeName(QualifiedInterfaceName), "Get")
.WithModifiers(TokenList(Token(SyntaxKind.PublicKeyword), Token(SyntaxKind.StaticKeyword)))
.WithParameterList(
ParameterList(
SingletonSeparatedList<ParameterSyntax>(
Parameter(Identifier(paramName))
.WithType(PredefinedType(Token(SyntaxKind.StringKeyword)))
)
)
)
.WithBody(
Block(
SwitchStatement(IdentifierName("insuranceName"), List(
GenerateCases(compilation, insuranceTypes).Append(
SwitchSection(
SingletonList<SwitchLabelSyntax>(DefaultSwitchLabel()),
SingletonList<StatementSyntax>(
ParseStatement(@$"throw new ArgumentException(nameof({paramName}), $""Insurance not found for name '{{{paramName}}}'."");")
)
)
)
))
)
)
)
);
}

private IEnumerable<SwitchSectionSyntax> GenerateCases(Compilation compilation, IEnumerable<ITypeSymbol> insuranceTypes)
{
foreach (var insuranceType in insuranceTypes)
{
var label = insuranceType.Name!;
var switchLabel = CaseSwitchLabel(LiteralExpression(SyntaxKind.StringLiteralExpression).WithToken(Literal(label)));
var typeName = compilation.GetTypeByMetadataName(insuranceType.ToString()!)!;
var instanceExpression = ReturnStatement(
ObjectCreationExpression(ParseTypeName(typeName.ToString()!))
.WithArgumentList(ArgumentList())
);
yield return SwitchSection(
SingletonList<SwitchLabelSyntax>(switchLabel),
SingletonList<StatementSyntax>(instanceExpression)
);
}
}

public void Initialize(GeneratorInitializationContext context)
{
}
}

这将生成如下所示的源代码:

namespace MyNamespace
{
public static class InsuranceFactory
{
public static InsuranceCompany.IInsurance Get(string insuranceName)
{
switch (insuranceName)
{
case "MassMutualLifeInsurance":
return new InsuranceCompany.MassMutual.MassMutualLifeInsurance();
case "GeicoLifeInsurance":
return new InsuranceCompany.Geico.GeicoLifeInsurance();
case "GeicoAutoInsurance":
return new InsuranceCompany.Geico.GeicoAutoInsurance();
default:
throw new ArgumentException(nameof(insuranceName), $"Insurance not found for name '{insuranceName}'.");
}
}
}
}

出于您的目的,您可能希望在您的类型上定义一个您希望参与该工厂的属性。这样您就可以更好地控制为案例生成的 insurnaceName

关于c# - 用源发生器代替反射,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/66793030/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com