如何使用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
相关产品推荐
相关产品推荐

