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容器實現具有以下功能:
- 支持類的定製屬性,指定其是否可被IoC容器掃描,以及如何實例化。
- 支持對象的單實例化(僅限默認構造函數)和多實例化;
以上兩點基本可以滿足部分中小型項目開發的應用場景了。
3. Tiny版IoC的實現
開發之前,你需要一些.NET for C#語言的知識儲備:
- 定製屬性
- 反射
- 泛型
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實現
說明:
- 利用反射機制獲取程序集中的所有包含定製屬性標記的類型;
- 根據定製屬性類型進行實例化;
- 將該實例化對象,存放到IoC容器字典中;
- 單實例化對象,直接從IoC容器中取;
- 多實例化對象,也可由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();
}
}
執行,得到了你想要的實例化結果: