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

如何使用Roslyn生成包含调用关系的C#项目自动化文档?

Roslyn获取方法/类调用依赖的实现方案

你之前只通过语法树获取节点信息的方式只能拿到调用的文本标识,无法区分同名方法/类、也无法获取跨项目引用的符号信息,必须搭配语义模型SemanticModel实现需求,具体实现步骤如下:

1. 加载全解决方案的编译上下文

使用MSBuildWorkspace加载所有解决方案,确保公共类库、跨项目引用的符号都被正确加载,避免跨项目调用识别失败:

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.MSBuild;
using Microsoft.Build.Locator;

// 非VS环境运行时需要先注册MSBuild路径,VS内运行可省略
MSBuildLocator.RegisterDefaults();

var workspace = MSBuildWorkspace.Create();
// 遍历所有你需要加载的sln文件
var solution = await workspace.OpenSolutionAsync(@"你的解决方案路径.sln");

foreach (var project in solution.Projects)
{
    // 获取当前项目的编译对象,包含所有引用的程序集符号
    var compilation = await project.GetCompilationAsync();
    if (compilation == null) continue;

    foreach (var syntaxTree in compilation.SyntaxTrees)
    {
        // 拿到需求的第一列:文件路径
        var filePath = syntaxTree.FilePath;
        var semanticModel = compilation.GetSemanticModel(syntaxTree);
        var root = await syntaxTree.GetRootAsync();

        // 遍历类的逻辑你已实现,直接补充调用依赖获取逻辑
        var classes = root.DescendantNodes().OfType<ClassDeclarationSyntax>();
        foreach (var cls in classes)
        {
            var classSymbol = semanticModel.GetDeclaredSymbol(cls);
            // 需求第二列:类名,ToDisplayString可返回带命名空间的完整类名
            var className = classSymbol?.ToDisplayString() ?? cls.Identifier.Text;
            // 类的注释你已可通过语法trivia获取
            var comment = GetCommentFromTrivia(cls);
            // 需求第三列:当前类调用的所有方法、类名集合
            var invokedNames = GetAllInvokedNames(cls, semanticModel);
            
            // 此处将四个字段存入你的数组即可
        }

        // 遍历方法的逻辑同上
        var methods = root.DescendantNodes().OfType<MethodDeclarationSyntax>();
        foreach (var method in methods)
        {
            var methodSymbol = semanticModel.GetDeclaredSymbol(method);
            var methodName = methodSymbol?.ToDisplayString() ?? method.Identifier.Text;
            var comment = GetCommentFromTrivia(method);
            var invokedNames = GetAllInvokedNames(method, semanticModel);
            
            // 存入数组即可
        }
    }
}

2. 实现调用依赖提取的辅助方法

遍历节点下所有方法调用、对象创建、类型引用节点,通过语义模型获取对应符号后提取名称:

List<string> GetAllInvokedNames(SyntaxNode node, SemanticModel semanticModel)
{
    var symbols = new HashSet<ISymbol>();

    // 收集所有方法调用
    foreach (var inv in node.DescendantNodes().OfType<InvocationExpressionSyntax>())
    {
        var symbol = semanticModel.GetSymbolInfo(inv).Symbol;
        if (symbol != null) symbols.Add(symbol);
    }

    // 收集所有new操作对应的类、构造函数
    foreach (var creation in node.DescendantNodes().OfType<ObjectCreationExpressionSyntax>())
    {
        var symbol = semanticModel.GetSymbolInfo(creation).Symbol;
        if (symbol is IMethodSymbol ctor)
        {
            symbols.Add(ctor);
            symbols.Add(ctor.ContainingType);
        }
    }

    // 收集所有类型引用、静态类访问
    foreach (var access in node.DescendantNodes().OfType<IdentifierNameSyntax>())
    {
        var symbol = semanticModel.GetSymbolInfo(access).Symbol;
        if (symbol is ITypeSymbol typeSym) symbols.Add(typeSym);
    }

    // 过滤只保留解决方案内的自定义符号(去掉系统类库依赖的话加这个判断)
    // symbols = symbols.Where(s => s.Locations.Any(l => l.IsInSource)).ToHashSet();

    // 返回需要的名称格式,要完整命名空间+类/方法名用ToDisplayString,只要短名用Name
    return symbols.Select(s => s.ToDisplayString()).Distinct().ToList();
}

注意事项

  • 如果需要区分方法重载,直接用ISymbol.ToDisplayString()返回的结果即可,会自动携带参数列表信息
  • 加载解决方案失败的话,先检查项目是否可以正常编译,再确认Microsoft.Build.Locator包是否正确安装注册

内容的提问来源于stack exchange,提问作者olivier57

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.28 01:36:03