gpt4 book ai didi

c# - 如何使用 Roslyn 通过扩展方法、静态类中的方法和带有 ref/out 参数的方法访问调用

转载 作者:太空狗 更新时间:2023-10-29 21:57:49 27 4
gpt4 key购买 nike

我正在创建一个开源项目,用于创建 .NET UML 序列图,它利用名为 js-sequence-diagrams 的 javascript 库。我不确定 Roslyn 是否适合这项工作,但我想我会试一试,所以我整理了一些概念验证代码,这些代码试图获取所有方法及其调用,然后以一种形式输出这些调用可以通过 js-sequence-diagrams 解释。

代码会生成一些输出,但不会捕获所有内容。我似乎无法通过扩展方法捕获调用,静态类中静态方法的调用。

我确实看到了带有 out 参数的方法调用,但没有以任何扩展 BaseMethodDeclarationSyntax 的形式出现

这是代码(请记住,这是概念验证代码,所以我没有完全遵循最佳实践,但我不要求在这里进行代码审查……另外,我习惯使用任务,所以我我正在摆弄 await,但我不完全确定我是否正确使用了它)

https://gist.github.com/SoundLogic/11193841

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection.Emit;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.MSBuild;
using Microsoft.CodeAnalysis.FindSymbols;
using System.Collections.Immutable;

namespace Diagrams
{
class Program
{
static void Main(string[] args)
{
string solutionName = "Diagrams";
string solutionExtension = ".sln";
string solutionFileName = solutionName + solutionExtension;
string rootPath = @"C:\Workspace\";
string solutionPath = rootPath + solutionName + @"\" + solutionFileName;

MSBuildWorkspace workspace = MSBuildWorkspace.Create();
DiagramGenerator diagramGenerator = new DiagramGenerator( solutionPath, workspace );
diagramGenerator.ProcessSolution();

#region reference

//TODO: would ReferencedSymbol.Locations be a better way of accessing MethodDeclarationSyntaxes?
//INamedTypeSymbol programClass = compilation.GetTypeByMetadataName("DotNetDiagrams.Program");

//IMethodSymbol barMethod = programClass.GetMembers("Bar").First(s => s.Kind == SymbolKind.Method) as IMethodSymbol;
//IMethodSymbol fooMethod = programClass.GetMembers("Foo").First(s => s.Kind == SymbolKind.Method) as IMethodSymbol;

//ITypeSymbol fooSymbol = fooMethod.ContainingType;
//ITypeSymbol barSymbol = barMethod.ContainingType;

//Debug.Assert(barMethod != null);
//Debug.Assert(fooMethod != null);

//List<ReferencedSymbol> barReferencedSymbols = SymbolFinder.FindReferencesAsync(barMethod, solution).Result.ToList();
//List<ReferencedSymbol> fooReferencedSymbols = SymbolFinder.FindReferencesAsync(fooMethod, solution).Result.ToList();

//Debug.Assert(barReferencedSymbols.First().Locations.Count() == 1);
//Debug.Assert(fooReferencedSymbols.First().Locations.Count() == 0);

#endregion

Console.ReadKey();
}
}

class DiagramGenerator
{
private Solution _solution;

public DiagramGenerator( string solutionPath, MSBuildWorkspace workspace )
{
_solution = workspace.OpenSolutionAsync(solutionPath).Result;
}

public async void ProcessSolution()
{
foreach (Project project in _solution.Projects)
{
Compilation compilation = await project.GetCompilationAsync();
ProcessCompilation(compilation);
}
}

private async void ProcessCompilation(Compilation compilation)
{
var trees = compilation.SyntaxTrees;

foreach (var tree in trees)
{
var root = await tree.GetRootAsync();
var classes = root.DescendantNodes().OfType<ClassDeclarationSyntax>();

foreach (var @class in classes)
{
ProcessClass( @class, compilation, tree, root );
}
}
}

private void ProcessClass(
ClassDeclarationSyntax @class
, Compilation compilation
, SyntaxTree tree
, SyntaxNode root)
{
var methods = @class.DescendantNodes().OfType<MethodDeclarationSyntax>();

foreach (var method in methods)
{
var model = compilation.GetSemanticModel(tree);
// Get MethodSymbol corresponding to method
var methodSymbol = model.GetDeclaredSymbol(method);
// Get all InvocationExpressionSyntax in the above code.
var allInvocations = root.DescendantNodes().OfType<InvocationExpressionSyntax>();
// Use GetSymbolInfo() to find invocations of target method
var matchingInvocations =
allInvocations.Where(i => model.GetSymbolInfo(i).Symbol.Equals(methodSymbol));

ProcessMethod( matchingInvocations, method, @class);
}

var delegates = @class.DescendantNodes().OfType<DelegateDeclarationSyntax>();

foreach (var @delegate in delegates)
{
var model = compilation.GetSemanticModel(tree);
// Get MethodSymbol corresponding to method
var methodSymbol = model.GetDeclaredSymbol(@delegate);
// Get all InvocationExpressionSyntax in the above code.
var allInvocations = tree.GetRoot().DescendantNodes().OfType<InvocationExpressionSyntax>();
// Use GetSymbolInfo() to find invocations of target method
var matchingInvocations =
allInvocations.Where(i => model.GetSymbolInfo(i).Symbol.Equals(methodSymbol));

ProcessDelegates(matchingInvocations, @delegate, @class);
}

}

private void ProcessMethod(
IEnumerable<InvocationExpressionSyntax> matchingInvocations
, MethodDeclarationSyntax methodDeclarationSyntax
, ClassDeclarationSyntax classDeclarationSyntax )
{
foreach (var invocation in matchingInvocations)
{
MethodDeclarationSyntax actingMethodDeclarationSyntax = null;
if (SyntaxNodeHelper.TryGetParentSyntax(invocation, out actingMethodDeclarationSyntax))
{
var r = methodDeclarationSyntax;
var m = actingMethodDeclarationSyntax;

PrintCallerInfo(
invocation
, classDeclarationSyntax
, m.Identifier.ToFullString()
, r.ReturnType.ToFullString()
, r.Identifier.ToFullString()
, r.ParameterList.ToFullString()
, r.TypeParameterList != null ? r.TypeParameterList.ToFullString() : String.Empty
);
}
}
}

private void ProcessDelegates(
IEnumerable<InvocationExpressionSyntax> matchingInvocations
, DelegateDeclarationSyntax delegateDeclarationSyntax
, ClassDeclarationSyntax classDeclarationSyntax )
{
foreach (var invocation in matchingInvocations)
{
DelegateDeclarationSyntax actingMethodDeclarationSyntax = null;

if (SyntaxNodeHelper.TryGetParentSyntax(invocation, out actingMethodDeclarationSyntax))
{
var r = delegateDeclarationSyntax;
var m = actingMethodDeclarationSyntax;

PrintCallerInfo(
invocation
, classDeclarationSyntax
, m.Identifier.ToFullString()
, r.ReturnType.ToFullString()
, r.Identifier.ToFullString()
, r.ParameterList.ToFullString()
, r.TypeParameterList != null ? r.TypeParameterList.ToFullString() : String.Empty
);
}
}
}

private void PrintCallerInfo(
InvocationExpressionSyntax invocation
, ClassDeclarationSyntax classBeingCalled
, string callingMethodName
, string returnType
, string calledMethodName
, string calledMethodArguments
, string calledMethodTypeParameters = null )
{
ClassDeclarationSyntax parentClassDeclarationSyntax = null;
if (!SyntaxNodeHelper.TryGetParentSyntax(invocation, out parentClassDeclarationSyntax))
{
throw new Exception();
}

calledMethodTypeParameters = calledMethodTypeParameters ?? String.Empty;

var actedUpon = classBeingCalled.Identifier.ValueText;
var actor = parentClassDeclarationSyntax.Identifier.ValueText;
var callInfo = callingMethodName + "=>" + calledMethodName + calledMethodTypeParameters + calledMethodArguments;
var returnCallInfo = returnType;

string info = BuildCallInfo(
actor
, actedUpon
, callInfo
, returnCallInfo);

Console.Write(info);
}

private string BuildCallInfo(string actor, string actedUpon, string callInfo, string returnInfo)
{
const string calls = "->";
const string returns = "-->";
const string descriptionSeparator = ": ";

string callingInfo = actor + calls + actedUpon + descriptionSeparator + callInfo;
string returningInfo = actedUpon + returns + actor + descriptionSeparator + "returns " + returnInfo;

callingInfo = callingInfo.RemoveNewLines(true);
returningInfo = returningInfo.RemoveNewLines(true);

string result = callingInfo + Environment.NewLine;
result += returningInfo + Environment.NewLine;

return result;
}
}

static class SyntaxNodeHelper
{
public static bool TryGetParentSyntax<T>(SyntaxNode syntaxNode, out T result)
where T : SyntaxNode
{
// set defaults
result = null;

if (syntaxNode == null)
{
return false;
}

try
{
syntaxNode = syntaxNode.Parent;

if (syntaxNode == null)
{
return false;
}

if (syntaxNode.GetType() == typeof (T))
{
result = syntaxNode as T;
return true;
}

return TryGetParentSyntax<T>(syntaxNode, out result);
}
catch
{
return false;
}
}
}

public static class StringEx
{
public static string RemoveNewLines(this string stringWithNewLines, bool cleanWhitespace = false)
{
string stringWithoutNewLines = null;
List<char> splitElementList = Environment.NewLine.ToCharArray().ToList();

if (cleanWhitespace)
{
splitElementList.AddRange(" ".ToCharArray().ToList());
}

char[] splitElements = splitElementList.ToArray();

var stringElements = stringWithNewLines.Split(splitElements, StringSplitOptions.RemoveEmptyEntries);
if (stringElements.Any())
{
stringWithoutNewLines = stringElements.Aggregate(stringWithoutNewLines, (current, element) => current + (current == null ? element : " " + element));
}

return stringWithoutNewLines ?? stringWithNewLines;
}
}
}

如有任何指导,我们将不胜感激!

最佳答案

ProcessClass 方法中使用 methodSymbol 我采纳了 Andy 的建议并提出了以下内容(尽管我认为可能有更简单的方法来解决这个问题):

private async Task<List<MethodDeclarationSyntax>> GetMethodSymbolReferences( IMethodSymbol methodSymbol )
{
var references = new List<MethodDeclarationSyntax>();

var referencingSymbols = await SymbolFinder.FindCallersAsync(methodSymbol, _solution);
var referencingSymbolsList = referencingSymbols as IList<SymbolCallerInfo> ?? referencingSymbols.ToList();

if (!referencingSymbolsList.Any(s => s.Locations.Any()))
{
return references;
}

foreach (var referenceSymbol in referencingSymbolsList)
{
foreach (var location in referenceSymbol.Locations)
{
var position = location.SourceSpan.Start;
var root = await location.SourceTree.GetRootAsync();
var nodes = root.FindToken(position).Parent.AncestorsAndSelf().OfType<MethodDeclarationSyntax>();

references.AddRange(nodes);
}
}

return references;
}

以及通过将输出文本插入 js-sequence-diagrams 生成的结果图像(如果有人觉得它有用,我已经更新了 github gist 的完整源代码 - 我排除了方法参数,因此图表很容易理解,但可以选择重新打开这些参数):

编辑:

我已经更新了代码(请参阅 github gist),因此现在调用按调用顺序显示(基于通过 FindCallersAsync 的结果在调用方法中被调用方法的跨度开始位置):

enter image description here

关于c# - 如何使用 Roslyn 通过扩展方法、静态类中的方法和带有 ref/out 参数的方法访问调用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23245368/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com