Dependency Injection as a Graph Problem
At its core, a Dependency Injection (DI) container is not just a registry of services; it is a mechanism for solving a directed acyclic graph (DAG). Every application is composed of services that rely on other services, creating a chain of dependencies. When a framework resolves these services, it must traverse this graph to ensure that every dependency is instantiated before the service that requires it. If the graph contains a cycle (e.g., Service A depends on B, and B depends on A), the container will fail, as it cannot determine an order of operations.
The Mechanics of Resolution: Reflection and Sorting
To resolve these dependencies, a DI container performs two primary technical tasks:
- Constructor Inspection via Reflection: The container uses reflection to examine the constructors of requested services. By inspecting the types required by a constructor, the container identifies what needs to be built first. This process is recursive; the container must inspect the dependencies of the dependencies until it reaches a service with no requirements (a leaf node in the graph).
- Topological Sorting: Once the dependency tree is mapped, the container must determine the execution order. This is achieved through topological sorting. By ordering the nodes in the graph such that for every directed edge from node U to node V, U comes before V, the container ensures that all required services are ready for injection into the parent service.
Building a custom DI container reveals that the "magic" of framework methods like AddScoped or AddSingleton is simply the management of these object lifetimes within the context of this sorted graph. Understanding this process shifts the view of DI from a black-box framework feature to a predictable, algorithmic system for managing object lifecycles and service orchestration.