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
{
    /// <summary>
    /// 數據訪問接口
    /// </summary>
   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容器,用來實現依賴註入(Dependency Injection,DI),減少耦合的,Unity出自於偉大的微軟。

unity能夠做什麼呢,列舉部分如下:

1.Unity支持簡單對象創建,特別是分層對象結構和依賴,以簡化程序代碼。其包含一個編譯那些可能存在依賴於其他對象的對象實例機制。
2.Unity支持必要的抽象,其允許開發者在運行時或配置去指定依賴關系同時可以簡單的管理橫切點(AOP)。
3.Unity增加瞭推遲到容器組件配置的靈活性。其同樣支持一個容器層次的結構。
4.Unity擁有服務定位能力,對於一個程序在許多情況下重復使用組件來分離和集中功能是非常有用的。
5.Unity允許客戶端儲存或緩存容器。對於在ASP.NET Web applications中開發者將容器持久化於ASP.NET中的session或application中特別有效。
6.Unity擁有攔截能力,其允許開發者通過創建並執行handlers(在方法或屬性被調用到達之前)來為已存在的組件增加一個函數,並再次為返回調用結果。
7.Unity可以從標準配置系統中讀取配置信息,例如:XML文件,同時使用配置文件來配置容器。
8.Unity支持開發者實現自定義容器擴展,例如:你可以實現方法來允許額外的對象構造和容器特征,例如緩存。
9.Unity允許架構師和開發者在現代化的程序中更簡單的實現通用設計模式。

什麼情況下要使用unity呢?

1.所構建的系統依賴於健全的面向對象原則,但是大量不同的代碼交織在一起而難以維護。
2.構建的對象和類需要依賴其他對象或類。
3.依賴於復雜的或需要抽象的對象。
4.希望利用構造函數、方法或屬性的調用註入優勢。
5.希望管理對象實例的生命周期。
6.希望能夠在運行時管理並改變依賴關系。
7.希望在攔截方法或屬性調用的時候生成一個策略鏈或管道處理容器來實現橫切(AOP)任務。
8.希望在Web Application中的回發操作時能夠緩存或持久化依賴關系。

1、程序中安裝Unity

使用管理NuGet程序包來安裝Unity,在項目上右鍵,選擇管理NuGet程序包:

在搜索框裡面輸入Unity,點擊右側安裝按鈕進行安裝:

出現以下信息表示安裝成功:

2、使用Unity實現DI

先來看看最簡單的Unity實現方式:

IUnityContainer container = new UnityContainer();//1、定義一個空容器
container.RegisterType<IDbInterface, DbMSSQL>();//2、註冊類型,表示遇到IDbInterface的類型,創建DbMSSQL的實例
var db = container.Resolve<IDbInterface>();
Console.WriteLine(db.Insert());
Console.ReadKey();

結果:

從結果中可以看出,db是DbMSSQL類型的實例。

除瞭使用RegisterType註冊類型以外,還可以註冊一個實例,例如:

// 使用RegisterInstance註冊IDbInterface的實例:new DbMSSQL() 
container.RegisterInstance<IDbInterface>(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
{
    /// <summary>
    /// IOC():控制反轉,把程序上層對下層的依賴,轉移到第三方的容器來裝配
    ///          是程序設計的目標,實現方式包含瞭依賴註入和依賴查找(.net裡面隻有依賴註入)
    /// DI:依賴註入,是IOC的實習方式。
    /// </summary>
    class Program
    {
        static void Main(string[] args)
        {
            #region MyRegion
            //// 常規做法,即程序的上端,依賴於下端,依賴於細節
            //DbMSSQL mssql = new DbMSSQL();

            //// 通過抽象來依賴
            //IDbInterface dbInterface = new DbMSSQL();

            //IUnityContainer container = new UnityContainer();//1、定義一個空容器
            //container.RegisterType<IDbInterface, DbMSSQL>();//2、註冊類型,表示遇到IDbInterface的類型,創建DbMSSQL的實例
            //var db = container.Resolve<IDbInterface>();

            //// 使用RegisterInstance註冊IDbInterface的實例:new DbMSSQL()
            //container.RegisterInstance<IDbInterface>(new DbMSSQL());
            //Console.WriteLine(db.Insert());
            #endregion

            IUnityContainer container = new UnityContainer();
            container.RegisterType<IPhone, ApplePhone>();
            container.RegisterType<IMicrophone, Microphone>();
            container.RegisterType<IHeadphone, Headphone>();
            container.RegisterType<IPower, Power>();

            IPhone phone = container.Resolve<IPhone>();

            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.RegisterType<IDbInterface, DbMSSQL>();//2、註冊類型,表示遇到IDbInterface的類型,創建DbMSSQL的實例
container.RegisterType<IDbInterface, DbOracle>();//表示遇到IDbInterface的類型,創建DbMSSQL的實例
var db = container.Resolve<IDbInterface>();
Console.WriteLine(db.Insert());

運行結果:

從運行結果中可以看出,後面註冊的類型會把前面註冊的類型給覆蓋掉,那麼該如何解決呢?可以通過參數的方式來解決,代碼如下:

IUnityContainer container = new UnityContainer();//1、定義一個空容器
container.RegisterType<IDbInterface, DbMSSQL>("sql");//2、註冊類型,表示遇到IDbInterface的類型,創建DbMSSQL的實例
container.RegisterType<IDbInterface, DbOracle>("oracle");//表示遇到IDbInterface的類型,創建DbMSSQL的實例
var sql = container.Resolve<IDbInterface>("sql");
var oracle = container.Resolve<IDbInterface>("oracle");
Console.WriteLine(sql.Insert());
Console.WriteLine(oracle.Insert());

運行結果:

5、生命周期

生命周期及一個對象從創建到釋放中間經過的時間。先看下面的代碼:

IUnityContainer container = new UnityContainer();
container.RegisterType<IDbInterface, DbMSSQL>();
IDbInterface db1 = container.Resolve<IDbInterface>();
IDbInterface db2 = container.Resolve<IDbInterface>();
Console.WriteLine("HashCode:"+db1.GetHashCode().ToString());
Console.WriteLine("HashCode:" + db2.GetHashCode().ToString());
Console.WriteLine(object.ReferenceEquals(db1,db2));

結果:

表明db1和db2是兩個不同的實例,即默認情況下生命周期是瞬時的,每次都是創建一個新的實例。

container.RegisterType<IDbInterface, DbMSSQL>(new TransientLifetimeManager());表示是瞬時生命周期,默認情況下即這種。

在看下面的代碼:

IUnityContainer container = new UnityContainer();
container.RegisterType<IDbInterface, DbMSSQL>(new ContainerControlledLifetimeManager());
IDbInterface db1 = container.Resolve<IDbInterface>();
IDbInterface db2 = container.Resolve<IDbInterface>();
Console.WriteLine("HashCode:" + db1.GetHashCode().ToString());
Console.WriteLine("HashCode:" + db2.GetHashCode().ToString());
Console.WriteLine(object.ReferenceEquals(db1, db2));

結果:

上圖的結果可以看出,db1和db2是同一個實例。

container.RegisterType(new ContainerControlledLifetimeManager())表示是容器單例,每次都是同一個實例。

6、使用配置文件實現

在上面的例子中,所有的例子都是一直在依賴於細節,那麼怎麼解決不依賴於細節呢?答案是隻能使用配置文件,配置文件如下:

<configuration>
  <configSections>
    <section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection, Unity.Configuration"/>
  </configSections>
  <unity>
    <sectionExtension type="Microsoft.Practices.Unity.InterceptionExtension.Configuration.InterceptionConfigurationExtension, Unity.Interception.Configuration"/>
    <containers>
      <container name="testContainer">
        <!--逗號前面是接口類型的完全限定名:命名空間+接口名稱,逗號後面是DLL文件的名稱 name解決同一個接口不同實例問題-->
        <register type="DataBase.Interface.IDbInterface,DataBase.Interface" mapTo="DataBase.MSSQL.DbMSSQL, DataBase.MSSQL" name="sql"/>
        <register type="DataBase.Interface.IDbInterface,DataBase.Interface" mapTo="DataBase.Oracle.DbOracle, DataBase.Oracle" name="oracle"/>
      </container>
    </containers>
  </unity>
</configuration>

註意:這個一個單獨的配置文件,要把屬性裡面的復制到輸出目錄改為始終復制,那麼這個配置文件才會生成到Debug目錄裡面。

程序如下:

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.Resolve<IDbInterface>("sql");
 Console.WriteLine(db.Insert());

結果:

觀察上面的代碼,會發現,如果改成使用配置文件的方式實現的話,代碼裡面就不會依賴於細節瞭,隻要一個接口類型。既然沒有細節瞭,那麼對項目進行如下的改造:把引用裡面對細節的引用都去掉(即去掉DataBase.MSSQL和DataBase.Oracle),然後Debug文件夾裡面沒有這兩個DLL瞭,但是這時需要把這兩個DLL復制到Debug目錄下面,否則程序運行的時候會找不到具體實現的類型。這樣就意味著程序架構隻依賴於接口。

引用裡面隻要對接口的引用瞭,沒有對具體實現的引用。去掉瞭對細節的依賴。

註意:使用配置文件實現時,必須把接口的具體實現類復制到程序目錄下面。

如果有額外添加瞭一種數據庫,那麼隻需要修改配置文件,把新的實現類復制到程序目錄下面即可實現程序的升級。

使用配置文件的時候,需要把UnityContainer容器定義為靜態的,這樣隻需要讀取一次配置文件即可。

到此這篇關於C#使用Unity實現IOC的文章就介紹到這瞭。希望對大傢的學習有所幫助,也希望大傢多多支持WalkonNet。

推薦閱讀: