16,IOC 容器Unity
DIP依赖倒置原则:
一种软件架构设计原则(抽象概念)。依赖抽象不依赖细节。
IOC控制反转(Inversion of Control
传统开发,上端依赖(调用/指定)下端对象,会有依赖,把对下端对象的依赖转移到第三方容器(工厂+配置+反射),使程序拥有更好的扩展性,是DIP 的具体实现方式,可以用来降低代码之间的额耦合度。
DI 即依赖注入(Dependency Injection):
- 是实现
IOC的手段和方法,就是能做到构造某个对象时,将依赖的对象自动初始化并注入。 - 有三种注入方式:构造方法注入--属性注入--方法注入(按照时间顺序)。
- 构造方法注入用的最多,默认找参数最多的构造方法,可以不用特性,可以去掉对容器的依赖。
16.1,Unity
微软推出的IOC框架,使用这个框架,可以实现AOP切面编程,便于代码的后期维护,此外该框架还自带单例模式,可以提高程序的运行效率。
安装NuGet包:Unity、Unity.Abstractions、Unity.Container

16.1.1,构造方法的注入
如果存在多个构造方法,且这些构造方法均适配依赖注入,那么默认情况下注入选择的是参数多的构造方法。但可通过[InjectionConstructor]特性指定特定的构造方法。
[InjectionConstructor]:标记指定的构造方法为构造方法注入
C#
public class TestServiceB : ITestService.ITestServiceB
{
int id = 10;
[InjectionConstructor]//使用特性指定注入时选择此构造方法
public TestServiceB(ITestService.ITestServiceA testServiceA)
{
}
//进行构造方法注入时默认选择参数较多的构造方法,可使用[InjectionConstructor]特性指定注入构造方法
public TestServiceB(ITestService.ITestServiceA testServiceA1, ITestService.ITestServiceA testServiceA2)
{
var reuslt = Object.ReferenceEquals(testServiceA1, testServiceA2);
}
public void PrintInfo()
{
Console.WriteLine("TestServiceB");
}
}
16.1.2,属性的注入
在构建某一个对象的时候,如果明确需要做属性注入,该对象中的需要注入的属性,就会根据属性的类型,创建出对象,赋值给属性。
[Dependency]:标记属性为属性注入
C#
[Dependency]//使用该特性进行属性注入
public ITestService.ITestServiceA TestServiceA { get; set; }
需要注意的是:注入顺序是首先注入构造方法,其次注入属性,再次注入方法 ,所以在构造方法里查看TestServiceA为null,当执行完构造方法后才再执行对属性的注入。
16.1.3,方法的注入
在构造某一个对象的时候,自动去执行某些方法,根据方法的参数类型,自动构造出参数的类型实例,传递到方法的参数中,就可以将这个参数注入到类的内部。
注意:方法的注入是在实例化对象的时候自动进行,不需要外部显式调用执行。方法类型需要为Public否则不能自动运行。
[InjectionMethod]:标记方法为方法注入
C#
[InjectionMethod]
public void PrintInnerInfo(ITestService.ITestServiceA testServiceA)
{
Console.WriteLine($"方法注入:在对象实例化中自动调用。TestServicA.Id={testServiceA.Id}");
}
16.2,生命周期
-
TransientLifetimeManager:瞬时生命周期 -
ContainerControlledLifetimeMannager: 单例生命周期 -
PerThreadLifetimeManager:线程单例生命周期
16.3,配置文件
安装Nuget包:Unity.Configuration

示例:
xml
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<!-- Unity配置节声明 -->
<section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection,Unity.Configuration"/>
</configSections>
<!-- Unity核心配置-->
<unity>
<containers>
<container name="Container">
<!-- ITestService.ITestServiceA:接口名;ITestService:接口所在的程序集 -->
<register type="ITestService.ITestServiceA,ITestService" mapTo="TestService.TestServiceA,TestService">
<!-- 瞬时生命周期,小写正确,可省略(默认就是transient) -->
<lifetime type="transient"></lifetime>
<!-- 单例写法,解开注释即用 -->
<!--<lifetime type="singleton"></lifetime>-->
</register>
<register type="ITestService.ITestServiceB,ITestService" mapTo="TestService.TestServiceB,TestService"></register>
</container>
</containers>
</unity>
</configuration>
16.3.1,配置别名
当一个接口被多个类实现的时候,就需要在配置中指明名称,否则将以最后一个类的实例作为对象进行注入。
C#
public class TestServiceA : ITestService.ITestServiceA
{
public int Id { get; set; } = 12;
public void PrintInfo()
{
Console.WriteLine("TestServiceA");
}
}
public class TestServiceAA : ITestService.ITestServiceA
{
public int Id { get ; set ; }
public void PrintInfo()
{
Console.WriteLine("这是TestServiceAA"); ;
}
}
通过属性name指定名称
xml
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<!-- Unity配置节声明 -->
<section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection,Unity.Configuration"/>
</configSections>
<!-- Unity核心配置-->
<unity>
<containers>
<container name="Container">
<!-- ITestService.ITestServiceA:接口名;ITestService:接口所在的程序集 -->
<!--通过name属性指定名称-->
<register name="testServiceA" type="ITestService.ITestServiceA,ITestService" mapTo="TestService.TestServiceA,TestService">
<!-- 瞬时生命周期,小写正确,可省略(默认就是transient) -->
<lifetime type="transient"></lifetime>
<!-- 单例写法,解开注释即用 -->
<!--<lifetime type="singleton"></lifetime>-->
</register>
<!--通过name属性指定名称-->
<register name="testServiceAA" type="ITestService.ITestServiceA,ITestService" mapTo="TestService.TestServiceAA,TestService">
</register>
<register type="ITestService.ITestServiceB,ITestService" mapTo="TestService.TestServiceB,TestService"></register>
</container>
</containers>
</unity>
</configuration>
通过名称选择注入
C#
ExeConfigurationFileMap mapfile = new ExeConfigurationFileMap();
mapfile.ExeConfigFilename = System.IO.Path.Combine(Environment.CurrentDirectory, "config/unity.config");
Configuration configuration = ConfigurationManager.OpenMappedExeConfiguration(mapfile, ConfigurationUserLevel.None);
var section = configuration.GetSection(UnityConfigurationSection.SectionName) as UnityConfigurationSection;
Unity.UnityContainer container = new Unity.UnityContainer();
container.LoadConfiguration(section, "Container");
var serviceA = container.Resolve<ITestService.ITestServiceA>("testServiceA");
var serviceAA = container.Resolve<ITestService.ITestServiceA>("testServiceAA");
16.3.2,配置构造方法注入
默认情况下,容器实例化对象选择的无参构造方法,若要选择指定的构造方法有以下两种方法:
- 第一种:在需要调用的构造方法上添加
[InjectionConstructor]特性,参考16.1.1节内容。 - 第二种:在配置文件中配置构造方法参数,在容器实例化中将根据参数类型,数量自动调用参数匹配的构造方法,这种方式不需要在构造方法上添加
[InjectionConstructor]特性。
xml
<register type="ITestService.ITestServiceA,ITestService" mapTo="TestService.TestServiceA,TestService">
<!-- 瞬时生命周期,小写正确,可省略(默认就是transient) -->
<lifetime type="transient"></lifetime>
<!-- 单例写法,解开注释即用 -->
<!--<lifetime type="singleton"></lifetime>-->
</register>
<register type="ITestService.ITestServiceB,ITestService" mapTo="TestService.TestServiceB,TestService">
<!--指定构造函数-->
<constructor>
<!--参数1-->
<!--name="id":表示构造方法的形参名为id-->
<!--因为是Int32类型没有进行注入所以这里需要添加value属性值-->
<param name="id" type="System.Int32" value="3"></param>
<!--参数2-->
<!--该参数是注入所以不需要添加value属性值,同时注册该类型的时候不能指定name否则将抛异常-->
<param name="testServiceA" ></param>
</constructor>
</register>
调用形参为id,testServiceA的构造方法
C#
public class TestServiceB : ITestService.ITestServiceB
{
int id = 10;
//[InjectionConstructor]//指定注入此构造方法
public TestServiceB(ITestService.ITestServiceA testServiceA)
{
}
public TestServiceB(int id)
{
this.id = id;
}
//配置将调用这个方法
public TestServiceB(int id,ITestService.ITestServiceA testServiceA)
{
this.id = id;
}
[Dependency]
public ITestService.ITestServiceA TestServiceA { get; set; }
//进行构造方法注入时默认优先使用参数较多的构造方法,可使用[InjectionConstructor]特性指定注入构造方法
public TestServiceB(ITestService.ITestServiceA testServiceA1, ITestService.ITestServiceA testServiceA2)
{
var reuslt = Object.ReferenceEquals(testServiceA1, testServiceA2);
}
}
特别注意:这里使用的ITestService.ITestServiceA配置时不能命名(即属性name赋值),命名后将抛异常。
错误示例:注册时设置了name属性
xml
<register name="testServiceA" type="ITestService.ITestServiceA,ITestService" mapTo="TestService.TestServiceA,TestService">
<!-- 瞬时生命周期,小写正确,可省略(默认就是transient) -->
<lifetime type="transient"></lifetime>
<!-- 单例写法,解开注释即用 -->
<!--<lifetime type="singleton"></lifetime>-->
</register>
执行var serviceB = container.Resolve<ITestService.ITestServiceB>();抛出异常:

16.3.3,配置属性注入
xml
<register type="ITestService.ITestServiceB,ITestService" mapTo="TestService.TestServiceB,TestService">
<!--指定构造函数-->
<constructor>
<!--参数1-->
<!--name="id":表示构造方法的形参名为id-->
<!--因为是Int32类型没有进行注入所以这里需要添加value属性值-->
<param name="id" type="System.Int32" value="3"></param>
<!--参数2-->
<!--该参数是注入所以不需要添加value属性值,同时注册该类型的时候不能指定name否则将抛异常-->
<param name="testServiceA" ></param>
</constructor>
<!--配置属性注入-->
<property name="TestServiceA"></property>
</register>
C#
// [Dependency]
//通过配置声明注入
public ITestService.ITestServiceA TestServiceA { get; set; }
16.3.4,配置方法注入
xml
<register type="ITestService.ITestServiceB,ITestService" mapTo="TestService.TestServiceB,TestService">
<!--指定构造函数-->
<constructor>
<!--参数1-->
<!--name="id":表示构造方法的形参名为id-->
<!--因为是Int32类型没有进行注入所以这里需要添加value属性值-->
<param name="id" type="System.Int32" value="3"></param>
<!--参数2-->
<!--该参数是注入所以不需要添加value属性值,同时注册该类型的时候不能指定name否则将抛异常-->
<param name="testServiceA" ></param>
</constructor>
<!--配置属性注入-->
<property name="TestServiceA"></property>
<!--配置方法注入-->
<method name="PrintConfigInfo">
<param name="testServiceA"></param>
<param name="id" type="System.Int32" value="100"></param>
</method>
</register>
注入的方法
C#
public void PrintConfigInfo(ITestService.ITestServiceA testServiceA,int id)
{
Console.WriteLine($"方法注入:在对象实例化中自动调用。TestServicA.Id={testServiceA.Id},注入的Id值为:{id}");
}
16.3.5,配置程序集位置
默认情况下,map To的程序集位于执行程序目录下,而如果不在执行程序目录下则需要进行如下配置:
注意是在app.config文件中,而不是自定义的config文件,在自定义的config文件中配置无效。
xml
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
</startup>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<!-- probePrivatePath:指定程序集的私有探测目录,多个目录用分号;分隔 -->
<!--指定 CLR 查找私有程序集的额外目录,lib 是相对于执行文件的相对路径-->
<probing privatePath="lib"/>
<!-- (可选)若有版本/公钥冲突,可添加程序集重定向 -->
<!--<dependentAssembly>
<assemblyIdentity name="TestService" publicKeyToken="null" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-9.9.9.9" newVersion="1.0.0.0"/>
</dependentAssembly>-->
</assemblyBinding>
</runtime>
</configuration>
Demo链接
https://download.csdn.net/download/lingxiao16888/92544537?spm=1001.2014.3001.5501