Как я могу получить классовые зависимости программно и их соответствующие местоположения файлов? - PullRequest
3 голосов
/ 14 мая 2019

Мне нужно получить какой-то граф зависимостей между классами данного проекта, то есть всеми классами, которые использует этот конкретный класс.Я хочу знать, какие классы использует данный класс, чтобы позже я мог найти их путь к файлу в проекте.Рассмотрим следующий простой пример:

public class Dog: Animal, IBark
{
    public void Bark()
    {
        // Code to bark.
    }

    public void Fight(Cat cat)
    {
        // Code to fight cat.
    }
}

Для этого конкретного примера я хотел бы знать, какие классы использует класс Dog.Поэтому я хотел бы программно получить доступ к объекту, который имеет эти зависимости.В этой ситуации этот объект будет содержать классы / интерфейсы IBark, Animal и Cat и, возможно, соответствующие им пути к файлам.

Возможно ли это в C #?Я попытался заглянуть в Roslyn API и, хотя я могу проанализировать документ и обойти его, чтобы найти узлы, я не нашел способа получить метаданные, связанные с этими узлами, которые могли бы дать мне то, что я ищу (например, пути к файлам),Это заставило меня задуматься, нет ли лучшего подхода к этой проблеме.

1 Ответ

1 голос
/ 17 мая 2019

Это можно сделать с помощью Roslyn apis.Алгоритм следующий:

  1. Загрузка решения (.sln)
  2. Итерация по проектам (.csproj)
  3. Итерация по документам (.cs) внутри проекта
  4. Загрузить semantic model для документа
  5. Получить SyntaxTree и пройти все SyntaxNode
  6. Определить каждый SyntaxNode конкретный тип.Если обнаруженный синтаксис является определением класса (скажем, Dog) - продолжайте обход, учитывая, что дальнейший обнаруженный класс или интерфейс зависят от Dog

Пример кода ниже.Также я совершил это в github .Вас заинтересуют примерные тестовые образцы и образец решения на основе вашего примера .Я сделал предположение, что один файл содержит только одно определение класса, однако я думаю, что этого должно быть достаточно для запуска.

        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);
...