本文详细讲解了C#使用Unity实现IOC的方法,文中通过示例代码介绍的非常详细。对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下 一、什么是IOC
在学习IOC之前,我们先来了解一个依赖诱导原理(DIP),这是IOC的核心原理。
原因:即上层模块不应该依赖下层模块,而是两者应该通过抽象相互依赖。依靠抽象,而不是细节。
我们先来看下面这个例子:
1.定义一个接口来封装数据库的基本CRUD操作。该接口定义如下:
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;using System.Data;namespace DataBase.Interface{ /// lt;summarygt; /// 数据访问接口 /// lt;/summarygt; public interface IDbInterface { string Insert(); string Delete(); string Update(); string Query(); }}
2.定义一个MSSQL类来实现这个接口,用来模拟SQLServer操作。MSSQL类定义如下:
using DataBase.Interface;using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;namespace DataBase.MSSQL{ public class DbMSSQL : IDbInterface { public string Delete() { return "MSSQL执行删除"; } public string Insert() { return "MSSQL执行插入"; } public string Query() { return "MSSQL执行查询"; } public string Update() { return "MSSQL执行更新"; } }}
3.定义一个Oracle类来实现这个接口,模仿Oracle的操作。Oracle类的定义如下:
using DataBase.Interface;using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;namespace DataBase.Oracle{ public class DbOracle : IDbInterface { public string Delete() { return "Oracle执行删除"; } public string Insert() { return "Oracle执行插入"; } public string Query() { return "Oracle执行查询"; } public string Update() { return "Oracle执行更新"; } }}
4.定义要调用的控制台应用程序:
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;using DataBase.Interface;using DataBase.MSSQL;namespace IOCConApp{ class Program { static void Main(string[] args) { // 常规做法,即程序的上端,依赖于下端,依赖于细节 DbMSSQL mssql = new DbMSSQL(); } }}
通常的做法是添加一个引用,然后直接实例化该类,但这将取决于具体的实现。现在代码修改如下:
// 通过抽象来依赖IDbInterface dbInterface = new DbMSSQL();
但是这个修改之后,虽然左边抽象了,但是右边还是要看细节。
那IOC到底是什么?
IOC(Inversion of Control)是一个重要的面向对象编程规则,用于减少程序之间的耦合问题,将程序上层对下层的依赖转移到第三方容器中进行组装。IOC是编程的目标,其实现包括依赖注入和依赖查找。英寸net,只有依赖注入。
说到IOC,就不得不说DI。DI:依赖注入,这是实现IOC的手段。
二、使用Unity实现IOC
Unity是一个IoC容器,用来实现依赖注入(DI)和减少耦合。团结来自伟大的微软。
unity能做什么?该列表如下:
1.Unity支持简单的对象创建,尤其是层次化的对象结构和依赖关系,以简化程序代码。它包含一种机制来编译那些可能依赖于其他对象的对象实例。
2。Unity支持必要的抽象,允许开发人员在运行时或配置时指定依赖关系,同时简单地管理横切点(AOP)。
3。Unity增加了延迟容器组件配置的灵活性。它还支持容器层次结构。
4。Unity具有定位服务的能力,这对于一个程序在很多情况下重用组件来分离和集中功能非常有用。
5。Unity允许客户端存储或缓存容器。对于开发人员来说,将ASP.NET的会话或应用程序中的容器持久化到ASP.NET的Web应用程序中尤其有效。
6。Unity具有拦截功能,允许开发人员通过创建和执行处理程序(在调用方法或属性之前)向现有组件添加一个函数,并再次调用返回的结果。
7。Unity可以从标准配置系统中读取配置信息,比如XML文件,并使用配置文件来配置容器。
8。Unity支持开发者实现定制的容器扩展。例如,您可以实现允许附加对象构造和容器功能(如缓存)的方法。
9。Unity允许架构师和开发人员在现代程序中更简单地实现通用设计模式。
什么时候应该使用unity?
1.构建的系统依赖于完善的面向对象原则,但是大量不同的代码交织在一起,难以维护。
2。构建的对象和类需要依赖于其他对象或类。
3。依赖于复杂或者抽象的物体。
4。希望利用构造函数、方法或者属性的调用注入。
5。您希望管理对象实例的生命周期。
6。我希望能够在运行时管理和更改依赖关系。
7。我希望在拦截方法或属性调用时,生成一个策略链或管道处理容器来实现横切(AOP)任务。
8。我希望我能在Web应用程序的回发操作中缓存或保持依赖关系。
1、程序中安装Unity
使用管理NuGet包安装Unity,右键单击项目并选择管理NuGet包:
在搜索框中输入Unity,然后单击右侧的安装按钮进行安装:
以下消息表明安装成功:
2、使用Unity实现DI
让我们先来看看最简单的Unity实现:
IUnityContainer container = new UnityContainer();//1、定义一个空容器container.RegisterTypelt;IDbInterface, DbMSSQLgt;();//2、注册类型,表示遇到IDbInterface的类型,创建DbMSSQL的实例var db = container.Resolvelt;IDbInterfacegt;();Console.WriteLine(db.Insert());Console.ReadKey();
结果:
从结果中可以看出,db是DbMSSQL类型的实例。
除了使用RegisterType注册类型之外,还可以注册实例,例如:
// 使用RegisterInstance注册IDbInterface的实例:new DbMSSQL() container.RegisterInstancelt;IDbInterfacegt;(new DbMSSQL());3、三种注入方式
三种注入方法:构造函数注入、属性注入和方法注入。
3.1 定义IHeadphone接口,代码如下:using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;namespace DataBase.Interface{ public interface IHeadphone { }}3.2 定义IMicrophone接口,代码如下:using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;namespace DataBase.Interface{ public interface IMicrophone { }}3.3 定义IPower接口,代码如下:using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;namespace DataBase.Interface{ public interface IPower { }}3.4 定义IPhone接口,代码如下:using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;namespace DataBase.Interface{ public interface IPhone { void Call(); IMicrophone iMicrophone { get; set; } IHeadphone iHeadphone { get; set; } IPower iPower { get; set; } }}3.5 分别实现上面定义的接口
IPhone界面的实现如下:
using DataBase.Interface;using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;using Unity.Attributes;namespace DataBase.MSSQL{ public class ApplePhone : IPhone { [Dependency]//属性注入 public IMicrophone iMicrophone { get; set; } public IHeadphone iHeadphone { get; set; } public IPower iPower { get; set; } [InjectionConstructor]//构造函数注入 public ApplePhone(IHeadphone headphone) { this.iHeadphone = headphone; Console.WriteLine("{0}带参数构造函数", this.GetType().Name); } public void Call() { Console.WriteLine("{0}打电话", this.GetType().Name); ; } [InjectionMethod]//方法注入 public void Init1234(IPower power) { this.iPower = power; } }}
IHeadphone接口的实现如下:
using DataBase.Interface;using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;namespace DataBase.MSSQL{ public class Headphone : IHeadphone { public Headphone() { Console.WriteLine("Headphone 被构造"); } }}
IMicrophone接口实现如下:
using DataBase.Interface;using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;namespace DataBase.MSSQL{ public class Microphone : IMicrophone { public Microphone() { Console.WriteLine("Microphone 被构造"); } }}
IPower接口实现如下:
using DataBase.Interface;using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;namespace DataBase.MSSQL{ public class Power : IPower { public Power() { Console.WriteLine("Power 被构造"); } }}
控制台程序调用:
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;using DataBase.Interface;using DataBase.MSSQL;using Unity;namespace IOCConApp{ /// lt;summarygt; /// IOC():控制反转,把程序上层对下层的依赖,转移到第三方的容器来装配 /// 是程序设计的目标,实现方式包含了依赖注入和依赖查找(.net里面只有依赖注入) /// DI:依赖注入,是IOC的实习方式。 /// lt;/summarygt; class Program { static void Main(string[] args) { #region MyRegion //// 常规做法,即程序的上端,依赖于下端,依赖于细节 //DbMSSQL mssql = new DbMSSQL(); //// 通过抽象来依赖 //IDbInterface dbInterface = new DbMSSQL(); //IUnityContainer container = new UnityContainer();//1、定义一个空容器 //container.RegisterTypelt;IDbInterface, DbMSSQLgt;();//2、注册类型,表示遇到IDbInterface的类型,创建DbMSSQL的实例 //var db = container.Resolvelt;IDbInterfacegt;(); //// 使用RegisterInstance注册IDbInterface的实例:new DbMSSQL() //container.RegisterInstancelt;IDbInterfacegt;(new DbMSSQL()); //Console.WriteLine(db.Insert()); #endregion IUnityContainer container = new UnityContainer(); container.RegisterTypelt;IPhone, ApplePhonegt;(); container.RegisterTypelt;IMicrophone, Microphonegt;(); container.RegisterTypelt;IHeadphone, Headphonegt;(); container.RegisterTypelt;IPower, Powergt;(); IPhone phone = container.Resolvelt;IPhonegt;(); Console.WriteLine($"phone.iHeadphone==null {phone.iHeadphone == null}"); Console.WriteLine($"phone.iMicrophone==null {phone.iMicrophone == null}"); Console.WriteLine($"phone.iPower==null {phone.iPower == null}"); Console.ReadKey(); } }}
输出结果:
从输出结果中,我们可以看到三种注入方法的执行顺序:构造函数注入、属性注入和方法注入。
注意:默认情况下,如果构造函数上没有特性,默认情况下会注入参数最多的构造函数。
4、一个接口多个实现进行注册
如果多个不同的实例实现同一个接口,这种情况如何注册?让我们先来看看下面的代码:
IUnityContainer container = new UnityContainer();//1、定义一个空容器container.RegisterTypelt;IDbInterface, DbMSSQLgt;();//2、注册类型,表示遇到IDbInterface的类型,创建DbMSSQL的实例container.RegisterTypelt;IDbInterface, DbOraclegt;();//表示遇到IDbInterface的类型,创建DbMSSQL的实例var db = container.Resolvelt;IDbInterfacegt;();Console.WriteLine(db.Insert());
运行结果:
从运行结果可以看出,后面注册的类型会覆盖前面注册的类型,那么如何解决呢?可以通过参数的方式来解决。代码如下:
IUnityContainer container = new UnityContainer();//1、定义一个空容器container.RegisterTypelt;IDbInterface, DbMSSQLgt;("sql");//2、注册类型,表示遇到IDbInterface的类型,创建DbMSSQL的实例container.RegisterTypelt;IDbInterface, DbOraclegt;("oracle");//表示遇到IDbInterface的类型,创建DbMSSQL的实例var sql = container.Resolvelt;IDbInterfacegt;("sql");var oracle = container.Resolvelt;IDbInterfacegt;("oracle");Console.WriteLine(sql.Insert());Console.WriteLine(oracle.Insert());
运行结果:
5、生命周期
生命周期以及对象创建和发布之间的时间。先看下面的代码:
IUnityContainer container = new UnityContainer();container.RegisterTypelt;IDbInterface, DbMSSQLgt;();IDbInterface db1 = container.Resolvelt;IDbInterfacegt;();IDbInterface db2 = container.Resolvelt;IDbInterfacegt;();Console.WriteLine("HashCode:"+db1.GetHashCode().ToString());Console.WriteLine("HashCode:" + db2.GetHashCode().ToString());Console.WriteLine(object.ReferenceEquals(db1,db2));
结果:
它显示了db1和db2是两个不同的实例,也就是说,默认情况下,生命周期是瞬时的,每次都会创建一个新的实例。
集装箱。RegisterTypelt。IDbInterface,DbMSSQLgt(new TransientLifetimeManager());是瞬时生命周期,这是默认值。
请看下面的代码:
IUnityContainer container = new UnityContainer();container.RegisterTypelt;IDbInterface, DbMSSQLgt;(new ContainerControlledLifetimeManager());IDbInterface db1 = container.Resolvelt;IDbInterfacegt;();IDbInterface db2 = container.Resolvelt;IDbInterfacegt;();Console.WriteLine("HashCode:" + db1.GetHashCode().ToString());Console.WriteLine("HashCode:" + db2.GetHashCode().ToString());Console.WriteLine(object.ReferenceEquals(db1, db2));
结果:
从上面的结果可以看出,db1和db2是同一个实例。
container . register type(new containercontrolledLifeTimeManager())表示是容器单体,每次都是同一个实例。
6、使用配置文件实现
在上面的例子中,所有的例子都一直依赖于细节,那么如何解决不依赖细节的问题呢?答案是只能用配置文件。配置文件如下所示:
lt;configurationgt; lt;configSectionsgt; lt;section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection, Unity.Configuration"/gt; lt;/configSectionsgt; lt;unitygt; lt;sectionExtension type="Microsoft.Practices.Unity.InterceptionExtension.Configuration.InterceptionConfigurationExtension, Unity.Interception.Configuration"/gt; lt;containersgt; lt;container name="testContainer"gt; lt;!--逗号前面是接口类型的完全限定名:命名空间+接口名称,逗号后面是DLL文件的名称 name解决同一个接口不同实例问题--gt; lt;register type="DataBase.Interface.IDbInterface,DataBase.Interface" mapTo="DataBase.MSSQL.DbMSSQL, DataBase.MSSQL" name="sql"/gt; lt;register type="DataBase.Interface.IDbInterface,DataBase.Interface" mapTo="DataBase.Oracle.DbOracle, DataBase.Oracle" name="oracle"/gt; lt;/containergt; lt;/containersgt; lt;/unitygt;lt;/configurationgt;
注意:对于这个单独的配置文件,属性中的复制到输出目录应该更改为总是复制,然后这个配置文件将生成到调试目录中。
该过程如下:
ExeConfigurationFileMap fileMap = new ExeConfigurationFileMap();fileMap.ExeConfigFilename = Path.Combine(AppDomain.CurrentDomain.BaseDirectory + "CfgFiles\\Unity.Config");//找配置文件的路径Configuration configuration = ConfigurationManager.OpenMappedExeConfiguration(fileMap, ConfigurationUserLevel.None); UnityConfigurationSection section = (UnityConfigurationSection)configuration.GetSection(UnityConfigurationSection.SectionName);IUnityContainer container = new UnityContainer();section.Configure(container, "testContainer");IDbInterface db = container.Resolvelt;IDbInterfacegt;("sql"); Console.WriteLine(db.Insert());
结果:
看上面的代码,你会发现,如果是用配置文件实现,代码就不会依赖细节,只需要一个接口类型。现在没有细节了,对项目进行如下修改:删除引用(即数据库)中对细节的所有引用。MSSQL和数据库。Oracle),然后Debug文件夹里就没有这两个dll了。不过这个时候你需要把这两个dll复制到调试目录下,否则程序运行的时候找不到具体的实现类型。这意味着程序架构只依赖于接口。
引用只指接口,不指具体实现。摆脱对细节的依赖。
注意:使用配置文件时,必须将接口的具体实现类复制到程序目录中。
如果添加了额外的数据库,则只能通过修改配置文件并将新的实现类复制到程序目录来升级程序。
使用配置文件时,需要将UnityContainer定义为static,所以只需要读取配置文件一次。
关于C#使用Unity实现IOC的这篇文章到此为止。希望对大家的学习有帮助
精彩评论