Мне нужно получить какой-то график зависимости между классами данного проекта, то есть всеми классами, которые использует этот конкретный класс. Я хочу знать, какие классы использует данный класс, чтобы позже я мог найти их путь к файлу в проекте. Рассмотрим следующий простой пример:
public class Dog: Animal, IBark
{
public void Bark()
{
// Code to bark.
}
public void Fight(Cat cat)
{
// Code to fight cat.
}
}
Для этого конкретного примера я хотел бы знать, какие классы использует класс Dog
. Поэтому я хотел бы программно получить доступ к объекту, который имеет эти зависимости. В этой ситуации этот объект будет содержать классы/интерфейсы IBark
, Animal
и Cat
и, возможно, соответствующие пути к файлам.
Возможно ли это в С#? Я попытался заглянуть в Roslyn API, и хотя я могу разобрать документ и просмотреть его, чтобы найти узлы, я не нашел способа получить метаданные, связанные с этими узлами, которые могли бы дать мне то, что я ищу (например, пути к файлам) . Это заставило меня задуматься, нет ли лучшего подхода к этой проблеме.
Это можно сделать с помощью Roslyn
apis. Алгоритм следующий:
semantic model
для документаSyntaxTree
и пройти все SyntaxNode
SyntaxNode
типа бетона. Если обнаруженный синтаксис является определением класса (скажем, Dog) — продолжайте обход, учитывая, что дальнейший обнаруженный класс или интерфейс зависят от Dog.Пример кода ниже. Также я совершил это в гитхаб. Вас будут интересовать образцы модульных тестов и пример решения на основе вашего примера. Я сделал предположение, что один файл содержит только одно определение класса, однако я думаю, что этого должно быть достаточно для начала.
var dependencies = new Dictionary<string, List<string>>();
//key - class name, value - list of dependent class names
var project = workspace.CurrentSolution.Projects.First();
foreach (var document in project.Documents)
{
var semanticModel = await document.GetSemanticModelAsync();
KeyValuePair<string, List<string>>? keyValue = null;
foreach (var item in semanticModel.SyntaxTree.GetRoot().DescendantNodes())
{
switch (item)
{
case ClassDeclarationSyntax classDeclaration:
case InterfaceDeclarationSyntax interfaceDeclaration:
if (!keyValue.HasValue)
{
keyValue = new KeyValuePair<string, List<string>>(semanticModel.GetDeclaredSymbol(item).Name, new List<string>());
}
break;
case SimpleBaseTypeSyntax simpleBaseTypeSyntax:
keyValue?.Value.Add(simpleBaseTypeSyntax.Type.ToString());
break;
case ParameterSyntax parameterSyntax:
keyValue?.Value.Add(parameterSyntax.Type.ToString());
break;
}
}
if (keyValue.HasValue)
{
dependencies.Add(keyValue.Value.Key, keyValue.Value.Value);
}
}
Для приведенного выше кода рабочая область загружается следующим образом:
var workspace = MSBuildWorkspace.Create();
await workspace.OpenSolutionAsync(solutionFilePath);
Спасибо, это звучит как хорошая отправная точка. В итоге я сделал что-то подобное, но все же узнал кое-что из вашего ответа.