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

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

示例:
<?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>
当一个接口被多个类实现的时候,就需要在配置中指明名称,否则将以最后一个类的实例作为对象进行注入。
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 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>
通过名称选择注入
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");
默认情况下,容器实例化对象选择的无参构造方法,若要选择指定的构造方法有以下两种方法:
第一种:在需要调用的构造方法上添加[InjectionConstructor]特性,参考16.1.1节内容。
第二种:在配置文件中配置构造方法参数,在容器实例化中将根据参数类型,数量自动调用参数匹配的构造方法,这种方式不需要在构造方法上添加[InjectionConstructor]特性。
<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的构造方法
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属性
<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>();抛出异常:

<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>
// [Dependency] //通过配置声明注入 public ITestService.ITestServiceA TestServiceA { get; set; }
<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>
注入的方法
public void PrintConfigInfo(ITestService.ITestServiceA testServiceA,int id) { Console.WriteLine($"方法注入:在对象实例化中自动调用。TestServicA.Id={testServiceA.Id},注入的Id值为:{id}"); }
默认情况下,map To的程序集位于执行程序目录下,而如果不在执行程序目录下则需要进行如下配置:
注意是在app.config文件中,而不是自定义的config文件,在自定义的config文件中配置无效。
<?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>
https://download.csdn.net/download/lingxiao16888/92544537?spm=1001.2014.3001.5501