首頁 > 軟體

C#使用Unity實現IOC

2022-03-08 19:00:41

一、什麼是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的文章就介紹到這了。希望對大家的學習有所幫助,也希望大家多多支援it145.com。


IT145.com E-mail:sddin#qq.com