---
id: ioc
title: 依赖注入容器(IOC)
---

### 定义

命名空间：TouchSocket.Core <br/>
程序集：[TouchSocket.Core.dll](https://www.nuget.org/packages/TouchSocket.Core)


## 一、说明

所谓依赖注入，是指程序运行过程中，如果需要调用另一个对象协助时，无须在代码中创建被调用者，而是依赖于外部的注入。通俗来讲，就是把有依赖关系的类放到容器中，然后在我们需要这些类时，容器自动解析出这些类的实例。依赖注入最大的好处时实现类的解耦，利于程序拓展、单元测试、自动化模拟测试等。依赖注入的英文为：Dependency Injection，简称 DI。（说明来自网络）

TouchSocket内置了**Container**容器。只需要引入TouchSocket.Core即可使用。

## 二、特点

- 支持构造函数、属性、方法三种注入方式，可以选择其中部分生效。
- 支持`Singleton`、`Transient`两种生命周期。
- 支持单接口，多实现注入。
- 支持当获取类型是可实例类型时，即使不注册，也能成功构造。
- 支持默认参数注入。
- 支持构建参数注入。
- 支持标签参数注入。
- 支持泛型注入。
- 支持Object注入。
- 支持**源生成注入**，全面接入AOT模式。

## 三、注入方式

### 3.1 构造函数注入

【定义类型】

```csharp showLineNumbers
class MyClass1
{

}

class MyClass2
{
    public MyClass2(MyClass1 myClass1)
    {
        this.MyClass1 = myClass1;
    }

    public MyClass1 MyClass1 { get; }
}
```

【注册和获取】

```csharp showLineNumbers
/// <summary>
/// 构造函数注入
/// </summary>
static void ConstructorInject()
{
    var container = GetContainer();
    container.RegisterSingleton<MyClass1>();
    container.RegisterSingleton<MyClass2>();

    var myClass1 = container.Resolve<MyClass1>();
    var myClass2 = container.Resolve<MyClass2>();

    Console.WriteLine(MethodBase.GetCurrentMethod().Name);
}
```
### 3.2 属性注入

使用**DependencyParamterInject**，或者**DependencyInject**标记属性，即可注入。

【定义类型】

```csharp showLineNumbers
class MyClass3
{
    /// <summary>
    /// 直接按类型，默认方式获取
    /// </summary>
    [DependencyInject]
    public MyClass1 MyClass1 { get; set; }

    /// <summary>
    /// 获得指定类型的对象，然后赋值到object
    /// </summary>
    [DependencyInject(typeof(MyClass2))]
    public object MyClass2 { get; set; }

    /// <summary>
    /// 按照类型+Key获取
    /// </summary>
    [DependencyParamterInject("key")]
    public MyClass1 KeyMyClass1 { get; set; }
}
```

【注册和获取】
```csharp showLineNumbers
static void PropertyInject()
{
    var container = GetContainer();
    container.RegisterSingleton<MyClass1>();
    container.RegisterSingleton<MyClass1>("key");
    container.RegisterSingleton<MyClass2>();

    container.RegisterSingleton<MyClass3>();

    var myClass3 = container.Resolve<MyClass3>();
    Console.WriteLine(MethodBase.GetCurrentMethod().Name);
}
```

:::tip 提示

`DependencyInject`特性中，Type和Key，可以同时使用，也可以只使用其中一个。

:::  

### 3.3 方法注入

使用**DependencyInject**标记属性，即可对方法注入。

【定义类型】
```csharp showLineNumbers
class MyClass4
{
    public MyClass1 MyClass1 { get;private set; }


    [DependencyInject]
    public void MethodInject(MyClass1 myClass1)
    {
        this.MyClass1 = myClass1;
    }
}
```

【注册和获取】
```csharp showLineNumbers
static void MethodInject()
{
    var container = GetContainer();
    container.RegisterSingleton<MyClass1>();
    container.RegisterSingleton<MyClass4>();

    var myClass4= container.Resolve<MyClass4>();
    Console.WriteLine(MethodBase.GetCurrentMethod().Name);
}
```

:::tip 提示

`DependencyInject`特性，也可以在方法注入时，对参数进行使用。

::: 

### 3.4 注入筛选

对于一个类，默认情况下，会支持`构造函数`、`属性`、`方法`三种注入方式。但是，当明确知道该类型仅会使用其中部分方式注入时，可以设置注入类型，以此节约性能。

```csharp {4}
/// <summary>
/// 让MyClass1仅支持构造函数和属性注入
/// </summary>
[DependencyType(DependencyType.Constructor | DependencyType.Property)]
class MyClass1
{

}
```

:::info 备注

Constructor构造函数配置不管配不配置，默认都是支持的。

:::  


### 3.5 默认参数实例化

当一个类，被**瞬时注入**后，在获取其实例时，可以按照实际情况重新获得其实际值。

例如：对于`MyClass5`，我们希望在**瞬时注入**后，获取其实例时，我们可以手动的重新指定`a`、`b`以及`MyClass1`的值。

```csharp showLineNumbers
class MyClass5
{
    public MyClass5(int a, string b, MyClass1 myClass1)
    {
        this.A = a;
        this.B = b;
        this.MyClass1 = myClass1;
    }

    public int A { get; }
    public string B { get; }
    public MyClass1 MyClass1 { get; }
}
```

那么我们可以这样实现。

```csharp showLineNumbers
var container = GetContainer();
container.RegisterSingleton<MyClass1>();
container.RegisterTransient<MyClass5>();//默认参数只在瞬时生命有效

var myClass5_1 = container.Resolve<MyClass5>(new object[] { 10, "hello" });
var myClass5_2 = container.Resolve<MyClass5>(new object[] { 20, "hi" });
var myClass5_3 = container.Resolve<MyClass5>(new object[] { 20, "hi", new MyClass1() });

//以下结果为true，因为在构建时并未覆盖MyClass1，所以MyClass1只能从容器获得，而MyClass1在容器是以单例注册，所以相同
var b1 = myClass5_1.MyClass1 == myClass5_2.MyClass1;

//以下结果为false，因为在构建时覆盖MyClass1，所以MyClass1是从ps参数直接传递的，所以不相同
var b2 = myClass5_1.MyClass1 == myClass5_3.MyClass1;
```

:::info 备注

默认参数实例化，仅在**瞬时注入**周期中适用。

:::  

## 四、源生成IOC容器

使用源生成IOC容器，能实现100%代码静态生成。能够有效解决运行时代码效率问题和AOT反射问题。

然后声明自己的容器，遵循如下：

1. 必须继承`ManualContainer`。
2. 必须使用`partial`修饰为部分类。
3. 必须添加特性`GeneratorContainer`。

```csharp showLineNumbers
 [GeneratorContainer]
 partial class MyContainer : ManualContainer
 {

 }
```

### 4.1 注册类型

注册类型，可以直接添加特性即可：

```csharp showLineNumbers 
[AutoInjectForSingleton]//将声明的类型直接标识为单例注入
class MyClass12
{

}

[AutoInjectForTransient]//将声明的类型直接标识为瞬态注入
class MyClass13
{

}
```

### 4.2 注册现有类型

现有类型已经完成声明，所以无法直接添加特性。所以只能将特性添加在`ManualContainer`的继承类上。

```csharp showLineNumbers
[AddSingletonInject(typeof(IInterface10), typeof(MyClass10))]//直接添加现有类型为单例
[AddTransientInject(typeof(IInterface11), typeof(MyClass11))]//直接添加现有类型为瞬态
[GeneratorContainer]
partial class MyContainer : ManualContainer
{

}
```

:::tip 提示

源生成IOC容器，同样支持属性、方法、Key等注册配置，使用规则和第三节一致。

:::  


## 五、生命周期

生命周期是对注入构造的实例的有效性而言的。TouchSocket支持两种生命周期。

- Singleton：单例注入，当注入，并且实例化以后，全局唯一实例。
- Transient：瞬时注入，每次获取的实例都是新实例。

对于这两种，熟悉IOC的同学，相信都知道到。

## 六、使用ServiceCollection

请安装NuGet包：`TouchSocket.Core.DependencyInjection`。

然后直接使用`AspNetCoreContainer`替代`Container`。

```csharp showLineNumbers
static IContainer GetContainer()
{
    //return new Container();//默认IOC容器

    return new AspNetCoreContainer(new ServiceCollection());//使用Aspnetcore的容器
}
```

Config使用

```csharp showLineNumbers
var config=new TouchSocketConfig()//载入配置
                .UseAspNetCoreContainer(new ServiceCollection());//ServiceCollection可以使用现有的
```

[本文示例Demo](https://gitee.com/RRQM_Home/TouchSocket/tree/master/examples/Core/IocConsoleApp)
