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
以找到实现您的接口的类型,然后为每种类型生成案例。
这是您可以尝试的一种实现:(我无法测试生成器本身,但内容生成应该可以工作)
[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
案例的生成。
推荐阅读
- html - BEVM 是有效的还是我们可以单独使用修饰符?
- python - 选择随机节点时出错(Python)
- php - 具有两个图表系列的 Google 图表 (SQL/PHP)
- autodesk-forge - 如何在 Design Automation API 中创建数据转换活动?
- c# - Gmail Mail Api 在 Api 资源管理器中工作,但不在代码 c# 中
- imagemagick - 与 imagemagick 蒙太奇相反
- c++ - 如何将 boost::asio::ip::address_v6 IP 转换为 2 uint64_t 数字并从 2 uint64_t 转换为 v6 地址?
- jira - JIRA JQL:查找需要估计的卡片
- css - 具有边界半径的边界梯度
- sql - SQL 从所有列中生成一个字符串