C#源生成器中调用Define方法生成源码遇阻求助
我正在编写一个C#源生成器,设计逻辑是让用户创建标记[GenerateMySource]属性且实现IGenerateMySource接口的类,该接口包含返回SourceDefinition对象的static abstract void Define()方法。我的需求是为每个带该属性的类,调用其Define()方法获取返回值,再据此生成对应源码。
已实现的SyntaxReceiver代码
public class MySyntaxReceiver : ISyntaxReceiver { public List<ClassDeclarationSyntax> ApiDefinitions = new(); public void OnVisitSyntaxNode(SyntaxNode syntaxNode) { if (syntaxNode is ClassDeclarationSyntax classDeclaration) { foreach (var attributeList in classDeclaration.AttributeLists) { foreach (var attribute in attributeList.Attributes) { if (attribute.Name.ToString() == nameof(GenerateMySourceAttribute)) { ApiDefinitions.Add(classDeclaration); } } } } } }
源生成器Execute方法的疑问
在源生成器的Execute方法中,我希望调用目标类的Define()方法,但不知道如何获取对应的类型并调用:
[Generator] public class MySourceGenerator: ISourceGenerator { public void Initialize(GeneratorInitializationContext context) { context.RegisterForSyntaxNotifications(() => new MySyntaxReceiver()); } public void Execute(GeneratorExecutionContext context) { var receiver = (MySyntaxReceiver)context.SyntaxReceiver; foreach (var apiDefinition in receiver.ApiDefinitions) { SourceDefinition definition = apiDefinition.UnderlyingType.Define(); // 此处.UnderlyingType为虚构代码 // 使用definition进行操作 context.AddSource(name, source); } } }
补充尝试:编译到内存再反射调用的问题
我尝试过将目标语法树编译到内存再反射调用的方式,但遇到了异常:
public void Execute(GeneratorExecutionContext context) { var receiver = (MySyntaxReceiver)context.SyntaxReceiver; foreach (var apiDefinition in receiver.ApiDefinitions) { List<MetadataReference> references = new(); references.AddRange(context.Compilation.ExternalReferences); references.AddRange(context.Compilation.References); CSharpCompilation compilation = CSharpCompilation.Create("test.dll", new[] {apiDefinition.SyntaxTree}, references, new(OutputKind.DynamicallyLinkedLibrary) ); using MemoryStream memStream = new(); var res = compilation.Emit(memStream); memStream.Seek(0, SeekOrigin.Begin); Assembly ass = Assembly.Load(memStream.ToArray()); // 查找类型并反射调用方法 } }
编译结果显示成功,但调用ass.GetTypes()时抛出System.Reflection.ReflectionTypeLoadException异常。尝试输出到文件再读取也有同样问题,用dotpeek查看dll内容正常,推测是依赖未解析导致的。
解决方案
你的需求是可行的,但不能直接通过反射调用目标类的方法——源生成器运行在编译阶段,用户代码尚未完成编译,直接反射会遇到依赖缺失、加载上下文限制等问题。正确的做法是借助Roslyn的语义分析,提取Define()方法的语法内容并解析其返回值,而非执行方法。
具体实现步骤
完善语义检查逻辑
原有的SyntaxReceiver仅做语法层面匹配,需要在Execute阶段结合语义模型,确认类确实实现了目标接口且存在符合要求的Define()方法:public void Execute(GeneratorExecutionContext context) { var receiver = (MySyntaxReceiver)context.SyntaxReceiver; var compilation = context.Compilation; foreach (var classSyntax in receiver.ApiDefinitions) { // 获取类的语义符号 var model = compilation.GetSemanticModel(classSyntax.SyntaxTree); if (model.GetDeclaredSymbol(classSyntax) is not INamedTypeSymbol classSymbol) continue; // 检查是否实现IGenerateMySource接口 var interfaceSymbol = compilation.GetTypeByMetadataName("YourNamespace.IGenerateMySource"); if (interfaceSymbol == null || !classSymbol.AllInterfaces.Contains(interfaceSymbol)) continue; // 查找符合要求的Define()方法 var defineMethod = classSymbol.GetMembers("Define") .OfType<IMethodSymbol>() .FirstOrDefault(m => m.IsStatic && m.ReturnType.Name == "SourceDefinition"); if (defineMethod == null) continue; // 后续解析逻辑 } }解析Define()方法的返回值
找到Define()方法的语法节点后,提取其返回语句中的表达式,通过语义分析获取SourceDefinition的属性值:// 接上面的代码 // 找到Define()方法的语法节点 var methodSyntax = classSyntax.Members .OfType<MethodDeclarationSyntax>() .FirstOrDefault(m => m.Identifier.Text == "Define"); if (methodSyntax?.Body == null) continue; // 提取return语句中的表达式 var returnStatement = methodSyntax.Body.Statements .OfType<ReturnStatementSyntax>() .FirstOrDefault(); if (returnStatement?.Expression == null) continue; // 处理对象初始化器的情况 if (returnStatement.Expression is ObjectCreationExpressionSyntax objCreation) { var expressionModel = compilation.GetSemanticModel(methodSyntax.SyntaxTree); foreach (var initializer in objCreation.Initializer?.Expressions.OfType<AssignmentExpressionSyntax>() ?? Enumerable.Empty<AssignmentExpressionSyntax>()) { var propertyName = initializer.Left.ToString(); var propertyValue = expressionModel.GetConstantValue(initializer.Right); // 收集属性值用于生成源码 } } // 处理常量返回值的情况 else { var constantValue = compilation.GetSemanticModel(methodSyntax.SyntaxTree).GetConstantValue(returnStatement.Expression); if (constantValue.HasValue && constantValue.Value is SourceDefinition definition) { // 使用definition生成源码 var source = GenerateSourceFromDefinition(definition); context.AddSource($"{classSymbol.Name}.generated.cs", source); } }编译+反射方案的修复(不推荐)
若坚持使用编译+反射,需通过自定义AssemblyLoadContext处理依赖解析:var loadContext = new AssemblyLoadContext(null, isCollectible: true); loadContext.Resolving += (ctx, name) => { // 从编译引用中查找依赖程序集 foreach (var reference in context.Compilation.References) { if (reference is MetadataFileReference fileRef && Path.GetFileName(fileRef.Path) == $"{name.Name}.dll") { return ctx.LoadFromAssemblyPath(fileRef.Path); } } return null; }; using var memStream = new MemoryStream(); compilation.Emit(memStream); memStream.Seek(0, SeekOrigin.Begin); var assembly = loadContext.LoadFromStream(memStream); // 后续反射调用逻辑
关键注意事项
- 源生成器运行在编译阶段,用户代码尚未完成编译,语义分析是更可靠的实现方式。
- 确保目标项目使用C# 11+版本,以支持
static abstract接口方法。 - 需处理
Define()方法返回值的各种语法形式(常量、对象初始化器、方法调用等),针对性解析语法节点。
内容的提问来源于stack exchange,提问作者ScottishTapWater

