[英]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.