Как я могу программно получить зависимости классов и их соответствующие местоположения файлов?

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

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, и хотя я могу разобрать документ и просмотреть его, чтобы найти узлы, я не нашел способа получить метаданные, связанные с этими узлами, которые могли бы дать мне то, что я ищу (например, пути к файлам) . Это заставило меня задуматься, нет ли лучшего подхода к этой проблеме.

Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
4
0
837
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

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

  1. Загрузить решение (.sln)
  2. Перебор проектов (.csproj)
  3. Итерация по документам (.cs) в рамках проекта
  4. Загрузите semantic model для документа
  5. Получить SyntaxTree и пройти все SyntaxNode
  6. Обнаружение каждого 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);

Спасибо, это звучит как хорошая отправная точка. В итоге я сделал что-то подобное, но все же узнал кое-что из вашего ответа.

João Paiva 18.05.2019 21:17

Другие вопросы по теме