從0開始搭建一個IoC容器(C#版)

1. IoC簡介

IoC(Inversion of Control)翻譯爲“控制翻轉”,這個“翻轉”指的“獲得依賴對象的過程被翻轉了”。

IoC思想出現之前,我們想實例化一個對象,就必須在需要的地方new這個對象,然後才能使用這個對象中的成員。這樣做的雖然很方便,但是久而久之代碼中到處都是分散new的對象,且每個對象的生命週期都無法得到有效管理,最終導致對象管理成爲項目開發的一個沉重的包袱。

如何擺脫這種困境呢——那就專門找一個模塊做這個事情,這個模塊就是IoC容器(容器是一種形象的說法,IoC就像一個碗,裏面可以盛放對象,想要對象,不要再到處new了,直接從這個碗裏面取)。

IoC的實現使得獲取依賴對象的過程由自身管理變爲由IoC主動注入,因此,IoC還有一個更容易理解的別名“依賴注入”。

2. Tiny版IoC的功能

目前有大量的IoC實現框架,比如Java Spring框架,.NET autofac框架,這些框架非常強大,本身的功能已經超出最初IoC設計的初衷,熟練使用起來還是需要費一些時間的。如果你的項目不是很龐大,但是也想好好管理對象,使代碼清晰結構模塊化,不妨自己來實現一個IoC容器。

下面給出的Tiny版IoC容器實現具有以下功能:

  1. 支持類的定製屬性,指定其是否可被IoC容器掃描,以及如何實例化。
  2. 支持對象的單實例化(僅限默認構造函數)和多實例化;

以上兩點基本可以滿足部分中小型項目開發的應用場景了。

3. Tiny版IoC的實現

開發之前,你需要一些.NET for C#語言的知識儲備:

  1. 定製屬性
  2. 反射
  3. 泛型

IoC實現的核心,是“反射”機制。“定製屬性”和“泛型”只是幫助你開發出可配置、通用化的、更好用的IoC容器。

3.1 定製屬性

	using System;
	/// <summary>
	/// 自定義類使用本定製屬性時,將會被Ioc掃描,默認進行單實例化
	/// 自定義類不使用本定製屬性或使用本定製屬性且帶入"MultiInstance"參數,將會執行多實例化
	/// </summary>
	[AttributeUsage(AttributeTargets.Class)]
	public class DependenceInjection : Attribute
	{
	    public string InstanceType { get; set; }
	
	    /// <summary>
	    /// SingleInstance 單實例初始化,應用程序生命週期內只初始化一次;默認爲單實例初始化。
	    /// MultiInstance 多實例初始化,每次調用,都將重新初始化一次。
	    /// </summary>
	    /// <param name="instanceType"></param>
	    public DependenceInjection(string instanceType = "SingleInstance")
	    {
	        this.InstanceType = instanceType;
	    }
	}

3.2 IoC實現

說明:

  1. 利用反射機制獲取程序集中的所有包含定製屬性標記的類型;
  2. 根據定製屬性類型進行實例化;
  3. 將該實例化對象,存放到IoC容器字典中;
  4. 單實例化對象,直接從IoC容器中取;
  5. 多實例化對象,也可由IoC代理進行自定義構造。
	using System;
	using System.Collections.Generic;
	using System.Reflection;
	using System.IO;
	/// <summary>
    /// 簡易IoC容器類,可以通過定製屬性DependenceInjection,來指定需要IoC容器掃描的類
    /// </summary>
    public static class IocHelper
    {
        public static Dictionary<Type, object> IocContainer = new Dictionary<Type, object>();

        /// <summary>
        /// 檢索指定路徑的程序集,註冊帶定製屬性的類到IoC容器中
        /// </summary>
        /// <param name="assemblyPath">指定路徑的程序集</param>
        public static void Register(string assemblyPath)
        {
            if (Path.GetExtension(assemblyPath) != ".exe" && !File.Exists(assemblyPath))
                throw new Exception(string.Format("程序集({0})不存在!", assemblyPath));
            try
            {
                Assembly asb = Assembly.LoadFrom(assemblyPath);
                var objetcList = asb.GetTypes();

                if (objetcList.Any())
                {
                    foreach (var obj in objetcList)
                    {
                        if (DoSingleInstance(obj))
                        {
                            //如果該類包含定製的屬性且爲要求執行單實例,則直接實例化
                            var objectInstance = Activator.CreateInstance(obj, null);
                            if (objectInstance != null)
                                IocContainer.Add(obj, objectInstance);
                            else
                                throw new Exception(string.Format("實例化對象{0}失敗!", obj));
                        }

                    }
                }
               
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        /// <summary>
        /// 判斷執行單實例化還是多實例化
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        private static bool DoSingleInstance(Type type)
        {
            Attribute[] attrs = Attribute.GetCustomAttributes(type);

            foreach (var attr in attrs)
            {
                if (attr is DependenceInjection)
                {
                    var di = (DependenceInjection)attr;
                    if (di.InstanceType == "SingleInstance")
                    {
                        return true;
                    }
                }
            }

            return false;
        }

        /// <summary>
        /// 實例化:
        ///     單實例使用默認構造函數
        ///     多實例可以自定義構造函數
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="args"></param>
        /// <returns></returns>
        public static T Resolve<T>(params object[] args)
        {
            if (DoSingleInstance(typeof(T)))
            {
                if (IocContainer.ContainsKey(typeof(T)))
                    return (T)IocContainer[typeof(T)];
            }
            else
            {
                return (T)Activator.CreateInstance(typeof(T), args);
            }

            return default(T);
        }
    }

4. Tiny版IoC的使用

比較方便的使用場景是:直接讓IoC掃描自己所在的應用程序,在程序運行的最開始處,註冊應用程序中滿足定製屬性條件的對象。因爲可以定製類的屬性,因此我們可以做到IoC在掃描的時候,不會掃描到自己。

	//User類被定製爲可被IoC掃描的類,且只能單實例化(是用來默認參數)
	[DependenceInjection()]
    public class User
    {
        public void SayHello()
        {
            Console.WriteLine("Hello!");
        }
    }

	//Book類沒有被定製,因此不可被IoC掃描,但是可以由IoC代理進行構造初始化
    public class Book
    {
        public void GetName()
        {
            Console.WriteLine("《哈利波特》");
        }
    }

	//Book類被定製爲可被IoC掃描的類,且可以多實例化
    [DependenceInjection("MultyInstance")]
    public class Food
    {
        public Food(string name)
        {
            Name = name;
        }

        public string Name { get; set; }
        public void GetName()
        {
            Console.WriteLine(Name);
        }
    }

	public class Program
    {
        public static void Main(string[] args)
        {
        	//編譯生成的程序集IoC.exe路徑
            IocHelper.Register(@"C:\Users\Administrator\Documents\Visual Studio 2015\Projects\IoC\IoC\bin\Debug\IoC.exe");

            var user = IocHelper.Resolve<User>();
            user?.SayHello();

            var book = IocHelper.Resolve<Book>();
            book?.GetName();

            var food1 = IocHelper.Resolve<Food>("榴蓮餅");
            food1?.GetName();

            var food2 = IocHelper.Resolve<Food>("烤香腸");
            food2?.GetName();

            Console.ReadKey();
        }
    }

執行,得到了你想要的實例化結果:
IoC的使用

5. 參考

Spring的IOC原理[通俗解釋一下]
C#自定義應用程序上下文對象+IOC自己實現依賴注入

發佈了8 篇原創文章 · 獲贊 0 · 訪問量 368
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章