Dependency inversion 依赖反转

The direction of dependency within the application should be in the direction of abstraction, not implementation details. Most applications are written such that compile-time dependency flows in the direction of runtime execution. This produces a direct dependency graph. That is, if module A calls a function in module B, which calls a function in module C, then at compile time A will depend on B which will depend on C, as shown in Figure 4-1.

依赖注入 Dependency Injection - 图1
Figure 4-1. Direct dependency graph.

Applying the dependency inversion principle allows A to call methods on an abstraction that B implements, making it possible for A to call B at runtime, but for B to depend on an interface controlled by A at compile time (thus, inverting the typical compile-time dependency). At run time, the flow of program execution remains unchanged, but the introduction of interfaces means that different implementations of these interfaces can easily be plugged in.

依赖注入 Dependency Injection - 图2
Figure 4-2. Inverted dependency graph.

Dependency inversion is a key part of building loosely-coupled applications, since implementation details can be written to depend on and implement higher level abstractions, rather than the other way around. The resulting applications are more testable, modular, and maintainable as a result. The practice of dependency injection is made possible by following the dependency inversion principle.

Recommendations 使用 DI 的建议

  • async/await and Task based service resolution is not supported. C# does not support asynchronous constructors; therefore, the recommended pattern is to use asynchronous methods after synchronously resolving the service.
  • Avoid storing data and configuration directly in the service container. For example, a user’s shopping cart shouldn’t typically be added to the service container. Configuration should use the options pattern. Similarly, avoid “data holder” objects that only exist to allow access to some other object. It’s better to request the actual item via DI.
  • Avoid static access to services (for example, statically-typing IApplicationBuilder.ApplicationServices for use elsewhere).
  • Avoid using the service locator pattern. For example, don’t invoke GetService to obtain a service instance when you can use DI instead:

Incorrect:

  1. public class MyClass()
  2. {
  3. public void MyMethod()
  4. {
  5. var optionsMonitor =
  6. _services.GetService<IOptionsMonitor<MyOptions>>();
  7. var option = optionsMonitor.CurrentValue.Option;
  8. ...
  9. }
  10. }

Correct:

  1. public class MyClass
  2. {
  3. private readonly IOptionsMonitor<MyOptions> _optionsMonitor;
  4. public MyClass(IOptionsMonitor<MyOptions> optionsMonitor)
  5. {
  6. _optionsMonitor = optionsMonitor;
  7. }
  8. public void MyMethod()
  9. {
  10. var option = _optionsMonitor.CurrentValue.Option;
  11. ...
  12. }
  13. }

Like all sets of recommendations, you may encounter situations where ignoring a recommendation is required. Exceptions are rare—mostly special cases within the framework itself.

DI is an alternative to static/global object access patterns. You may not be able to realize the benefits of DI if you mix it with static object access.