Search code examples
c#reflectiondependency-injectionsystem.reflection

How can I generate a constructor dependency graph for a class or list of classes?


I would like to generate a text output list that traverses my constructor dependencies for a class or list of classes. I assume I would use reflection in some way to do this? And have protection against circular dependencies.

https://stackoverflow.com/a/29704045/254257 This seems to be what I would want, but they provided no code. That question is on a similar track, but they just assume you have start with a dictionary with your dependencies already outlined as strings. So I guess how would I get that to start with.

Say I have the following:

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

I would want some code to output something like this:

UserService

----GroupService

--------UserService CIRCULAR DEPENDENCY (stops going any deeper)

--------RoleService

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

--------LoggingService

----LoggingService

If I wanted to check dependencies on only the UserService, with the actual concrete implementation of the interfaces.

I know I can var type = typeof(UserService) as a starting point, but I've only ever worked with properties before so not sure what to do next.

I would imagine I would somehow need to get the constructor parameters, the types of those, then get the actual implementations and repeat, somehow also making sure I don't get stuck in a loop if I have any circular dependencies. Not sure how to do any of that so help would be appreciated.


Solution

  • Well it took some figuring out and it's probably not perfect, but for my code it worked. I started at Chetan's comment and just went down the rabbit hole. I made it a Utility:

    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>();
        }
    }