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

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

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

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

https://stackoverflow.com/questions/66793030/

相关文章:

r - 如何在 Apple Silicon (M1) Mac 上安装 RcppArmadillo

delphi - GDI+ DrawLine 什么都不画

firebase - 在 Flutter 中使用相同的 Firebase 将同一应用程序中的用户和卖

javascript - 如何使用react router dom显示详细信息页面

qt - 如何将 QML 文件组织到嵌套文件夹中?

go - 为什么在地址上取消引用会在 golang 中产生 "invalid indirect"错误

python - Pandas:Groupby 并使用剩余的列名和值创建字典

python-3.x - 根据索引和标签转换数据框

heroku - Unresolved 导入 `core::task::Wake`

ios - 如何将十六进制数据分解为来自 BLE 设备的可用数据? (速度和节奏)