Как я могу сгенерировать график зависимости конструктора для класса или списка классов? - PullRequest
0 голосов
/ 16 мая 2018

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

https://stackoverflow.com/a/29704045/254257 Кажется, это то, что я хотел бы, но они не предоставили код.Этот вопрос находится на аналогичной трассе, но они просто предполагают, что вы начали со словаря с вашими зависимостями, уже выделенными в виде строк.Поэтому я думаю, с чего бы это начать.

Скажем, у меня есть следующее:

public class UserService(IGroupService groupService, ILoggingService loggingService)
public class GroupService(IUserService userService, IRoleService roleService, ILoggingService loggingService)
public class RoleService(ILoggingService loggingService)

Я бы хотел, чтобы какой-то код вывел что-то вроде этого:

UserService

---- GroupService

-------- UserService ЦИРКУЛЯРНАЯ ЗАВИСИМОСТЬ (перестает идти глубже)

-------- RoleService

------------ LoggingService

-------- LoggingService

---- LoggingService

Если бы я хотел проверить зависимости только от UserService, с фактической конкретной реализацией интерфейсов.

Я знаю, что могу var type = typeof(UserService) в качестве отправной точки, но я только когда-либо работал со свойствами, поэтому не уверен, что делать дальше.

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

Ответы [ 2 ]

0 голосов
/ 16 мая 2018

Ну, это потребовало некоторого выяснения, и это, вероятно, не идеально, но для моего кода это сработало. Я начал с комментария Четана и просто спустился в кроличью нору. Я сделал это Утилита:

public static class DependencyChainUtil
{
    public static TypeModel GetDependencyChainForType(Type type)
    {
        var currentChainClassList = new List<string>();
        var model = GetDependencyChainForType(type, currentChainClassList);
        return model;
    }

    private static TypeModel GetDependencyChainForType(Type type, List<string> currentChainClassList)
    {
        if (type != null)
        {
            var model = new TypeModel() {Type = type};
            if (currentChainClassList.Any(x => x == type.FullName))
            {
                model.IsCircularReference = true;
            }
            else
            {

                currentChainClassList.Add(type.FullName);
                var constructorInfo = type.GetConstructors().Where(x => x.GetParameters().Length > 0);

                foreach (var info in constructorInfo)
                {
                    foreach (var parameterInfo in info.GetParameters())
                    {
                        var subType = parameterInfo.ParameterType;
                        if (subType.IsInterface)
                        {
                            var types = AppDomain.CurrentDomain.GetAssemblies()
                                .SelectMany(s => s.GetTypes()).Where(x => x.GetInterfaces().Contains(subType))
                                .ToList();
                            if (types.Any())
                            {
                                subType = types.FirstOrDefault();
                            }
                        }
                        model.ConstructorDependencies.Add(GetDependencyChainForType(subType, currentChainClassList));
                    }
                }

                currentChainClassList.Remove(type.FullName);
            }

            return model;
        }

        throw new ArgumentNullException("Parameter 'type' is null.");
    }

    public static string OutputTextOfDependencyChain(TypeModel model)
    {
        var output = "";
        var depth = 0;
        if (model != null)
        {
            output = OutputTextOfDependencyChain(model, output, depth);
        }

        return output;
    }

    private static string OutputTextOfDependencyChain(TypeModel model, string output, int depth)
    {
        //prepend depth markers
        output += new String(Enumerable.Range(0, depth*4).SelectMany(x => "-").ToArray());
        output += model.Type.Name;
        output += model.IsCircularReference ? "(CYCLIC DEPENDENCY)" : null;
        output += "<br/>";
        depth++;
        foreach (var typeModel in model.ConstructorDependencies)
        {
            output = OutputTextOfDependencyChain(typeModel, output, depth);
        }

        return output;
    }
}

public class TypeModel
{
    public Type Type { get; set; }
    public List<TypeModel> ConstructorDependencies { get; set; }
    public bool IsCircularReference { get; set; }

    public TypeModel()
    {
        ConstructorDependencies = new List<TypeModel>();
    }
}
0 голосов
/ 16 мая 2018

Проблема, с которой вы столкнулись, связана с тем, что вы используете DI-контейнер.Такая проблема с меньшей вероятностью возникает при использовании Pure DI , поскольку в этом случае компилятор C # будет проверять конструкцию объекта, и такую ​​циклическую зависимость практически невозможно получить.

Когдавы используете DI-контейнер, убедитесь, что вы используете DI-контейнер, который позволяет обнаруживать циклические зависимости и сообщает о существенной ошибке.На самом деле, любой из зрелых DI-контейнеров действительно очень четко сообщает об ошибках циклической зависимости .Если выбранный вами DI-контейнер генерирует исключение переполнения стека, рассмотрите возможность переключения на устаревший контейнер.

Простой инжектор , например, сгенерирует исключение со следующим сообщением:

Недопустимая конфигурация.Не удалось создать экземпляр для типа IGroupService.Тип GroupService прямо или косвенно зависит от самого себя.Циклический граф содержит следующие типы: GroupService -> UserService -> GroupService.

Другими словами, Simple Injector показывает циклический граф следующим образом:

GroupService ->UserService -> GroupService

Таким образом, вам не нужно визуализировать граф объектов, и фактически большинство контейнеров не смогут это сделать из-за циклической зависимости.Если ваш объектный граф был ациклическим, Simple Injector будет визуализировать график следующим образом при детализации контейнера с помощью отладчика Visual Studio:

enter image description here

ИлиВы можете достичь того же, используя программный API Simple Injector:

var graph = container.GetRegistration(typeof(UserService)).VisualizeObjectGraph();

В результате вы получите следующий текст:

UserService(
    GroupService(
        RoleService(
            LoggingService()),
        LoggingService()),
    LoggingService())

Обратите внимание, что пробег может отличаться от других контейнеров DI, ноОпять же, большинство более зрелых библиотек содержат эти типы функций.

...