references:
MicroSoft: dependency-injection
Youtube: YZK

生活中的“控制反转”: 自己发电(自己控制)和用电网的电(电网控制,用户使用)。
依赖注入(Dependency Injection,DI)是控制反转(Inversion of Control,IOC)思想的实现方式。
依赖注入简化模块的组装过程,降低模块之间的耦合度。

代码控制反转的目的:
“怎样创建xx对象” -> “我要xx对象”
两种实现方式:服务定位器(ServiceLocator),依赖注入(DI,主要方法)

DI几个概念:

  • Dependency: A dependency is an object that another object depends on.
  • 服务(service):向框架要的对象;
  • 注册服务:
  • 服务容器:负责管理注册的服务;
  • 查询服务:创建对象以及关联对象;
  • 对象生命周期:Transient(瞬态); scoped(范围);Singleton(单例);

.NET中使用DI:
根据类型来获取和注册服务。可以分别制定服务类型(service type)和实现类(implementation type)。
.NET控制反转组件取名为DependencyInjection, 它包含ServiceLocator的功能。

  • dotnet add package Microsoft.Extensions.DependencyInjection --version 9.0.1
  • using Microsoft.Extensions.DependencyInjection
  • Create a new ServiceCollection instance, then register and configure services in the ServiceCollection.
  • ServiceCollection来构造容器对象IServiceProvider。调用ServiceCollection的BuildServiceProvider创建ServiceProvider,可以用来获取BuildServiceProvider之前ServiceCollection中的对象。

服务的生命周期:

  • Transient(created each time they're requested from the service container.); Scoped(For Web applications, a scoped lifetime indicates that services are created once per client request/connection); Singleton(Every subsequent request of the service implementation from the dependency injection container uses the same instance, Singleton services must be thread safe and are often used in stateless services.)
  • 给类构造函数中打印,看看不同生命周期对象创建,使用serviceProvider.CreateScope()创建scope(在scope中获取Scope相关对象,scope.ServiceProvider而不是全局的ServiceProvider), 另外在范围内部的对象不要在范围外部引用,有可能对象已经在范围内部销毁了,外部引用为空;
  • 如果一个类实现了IDisposable接口,则离开作用域之后容器会自动调用对象的Dispose方法;
  • 不要在长生命周期的对象中引用比它短的生命周期对象,在ASP.NET Core中,这样做会默认抛异常。
  • 生命周期的选择:如果类无状态,建议为Singleton;如果类有状态,则有Scope控制,建议为Scoped,因为通常这种Scope控制下的代码都会运行在同一个线程中,没有并发修改的问题;在使用Transient的时候要谨慎。
  • .NET组册服务的重载方法很多。

Any of service registration methods can be used to register multiple service instances of the same service type. the following call overrides the previous one when resolving service, and adds to the previous one when multiple services are solved via IEnumerable, Services appear in the order they were registered when resolved via IEnumerable.

IServiceProvider的服务定位器方法:

  • T GetService()如果获取不到对象,则返回null
  • object GetService(Type serviceType)
  • T GetRequiredService()如果获取不到对象,则抛异常
  • object GetRequiredService(Type serviceType)
  • IEnumerable GetServices()适用于可能有很多满足条件的服务
  • IEnumerable GetServices(Type ServiceType)

依赖注入是有“传染性”的,如果一个类的对象是通过DI创建的,那么这个类的构造函数中生命的所有服务类型的参数都会被DI赋值;但是如果一个对象是程序员手动创建的,安么这个对象就和DI没有关系,它的构造函数中的生命的服务类型参数就不会被自动赋值;
.NET的DI默认是构造函数注入

e.g. 编写一个类,连接数据库做插入操作,并且记录日志(模拟的输出),把Dao、日志都放入单独的服务类。

using Microsoft.Extensions.DependencyInjection;

ServiceCollection services = new ServiceCollection();

services.AddScoped<Controller>();
services.AddScoped<ILog, LogImpl>();
services.AddScoped<IStorage, StorageImpl>();
services.AddScoped<IConfig, ConfigImpl>();

using (var sp = services.BuildServiceProvider())

{
// 所有的成员变量,都会被框架创建并赋值
var c = sp.GetRequiredService<Controller>();

c.Test();
}


class Controller

{
private readonly ILog log;

private readonly IStorage storage;

public Controller (ILog log, IStorage storage)

{
this.log = log;

this.storage = storage;

}


public void Test()

{
this.log.Log("start upload...");
this.storage.Save("sdflkjljioweuriowueroiweu", "1.txt");

this.log.Log("finish upload....");
}
}


interface ILog

{
public void Log(String msg);

}


class LogImpl : ILog

{
public void Log(string msg)

{
Console.WriteLine($"log: {msg}");

}
}


interface IConfig

{
public string GetValue(string name);

}


class ConfigImpl : IConfig

{
public string GetValue(string name)

{
return "hello";

}
}


interface IStorage

{
public void Save(string content, string name);

}


class StorageImpl : IStorage

{
private readonly IConfig config;

public StorageImpl(IConfig config)

{
this.config = config;

}
public void Save(string content, string name)

{
string server = config.GetValue("serve");

Console.WriteLine($"upload to {server}, file name is {name}, content is {content}");

}
}

Multiple constructor discovery rules

  • When a type defines more than one constructor, the service provider has logic for determining which constructor to use. The constructor with the most parameters where the types are DI-resolvable is selected.
  • If there's ambiguity when discovering constructors, an exception is thrown. You can avoid ambiguity by defining a constructor that accepts both DI-resolvable types instead.

总结:
关注于接口,而不是关注于实现,各个服务可以更弱耦合的协同工作。在编写代码的时候,不用知道具体服务的实现。

Author Of article : Alex Read full article