You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C#源生成器中调用Define方法生成源码遇阻求助

关于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()方法的语法内容并解析其返回值,而非执行方法。

具体实现步骤

  1. 完善语义检查逻辑
    原有的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;
            // 后续解析逻辑
        }
    }
    
  2. 解析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);
        }
    }
    
  3. 编译+反射方案的修复(不推荐)
    若坚持使用编译+反射,需通过自定义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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 13:13:18