簡體   English   中英

用源生成器替換反射

[英]Replacing Reflection with Source Generators

我有一個使用反射的工廠,我想用源生成器生成的反射來替換它。

生成的代碼應如下所示:

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 object 進行與通過反射代碼相同的搜索?

您必須使用編譯器通過執行上下文提供的等效 API。 然后根據您想要生成源的方式,您可以直接生成源文本或生成表示源的語法節點。

您需要在Compilation中四處挖掘以找到實現您的接口的類型,然后為每種類型生成案例。

這是您可以嘗試的一種實現:(我無法測試生成器本身,但內容生成應該可以工作)

[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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM