概述 IOC (Inversion of Control) 控制反轉,大家應該都比較熟悉了、應該也都有用過,這裡就不具體介紹了。自己平時也有用到過IOC,但是對它的具體實現原理只有一個模糊的概念,所以決定自己手動實現一個簡單IOC。 開始 首先呢我們得知道IOC的主要作用是什麼,才能開始動手寫。IO ...
概述
IOC (Inversion of Control) 控制反轉,大家應該都比較熟悉了、應該也都有用過,這裡就不具體介紹了。自己平時也有用到過IOC,但是對它的具體實現原理只有一個模糊的概念,所以決定自己手動實現一個簡單IOC。
開始
首先呢我們得知道IOC的主要作用是什麼,才能開始動手寫。IOC主要不就是負責創建對象以及管理生命周期嘛,那我們就開始動手啦。
比如現在有一個IAnimal介面Animal繼承介面,然後就是個Call的方法。一般我們使用的時候都是IAnimal animal=new Animal(); 如果是使用第三方IOC容器實現的話,我們需要先註冊一下類型才能獲取到實例。
所以我們先來個最簡單的仿照這個過程:
新建一個Container,然後裡面有一個類型註冊的方法ResgisterType和一個返回實例的方法Rerolve,還有一個存儲類型的字典,具體代碼如下
private static Dictionary<string, object> ContainerTypeDictionary = new Dictionary<string, object>();/// <summary> /// 註冊類型 /// </summary> /// <typeparam name="IT"></typeparam> /// <typeparam name="T"></typeparam> public void ResgisterType<IT,T>() { if (!ContainerTypeDictionary.ContainsKey(typeof(IT).FullName)) ContainerTypeDictionary.Add(typeof(IT).FullName, typeof(T)); } /// <summary> /// 根據註冊信息生成實例 /// </summary> /// <typeparam name="IT"></typeparam> /// <returns></returns> public IT Rerolve<IT>() { string key = typeof(IT).FullName; Type type = (Type)ContainerTypeDictionary[key]; return (IT)Activator.CreateInstance(type);
}
然後我們新建一個控制台測試一下
Container container = new Container(); container.ResgisterType<IAnimal, Animal>(); IAnimal animal= container.Rerolve<IAnimal>();
然後可以在不依賴具體對象Animal的情況下成功的創建一個animal實例。
之後我們就可以考慮複雜一點的情況了,現在我們的Animal類里沒有做任何事,假如它的構造函數里依賴於另一個對象呢,這樣我們的程式肯定是會報錯的。比如下麵這樣:
public class Animal: IAnimal { public Animal(Dog dog) { } }
我們容器目前能創建的對象實例,只有通過ResgisterType方法註冊過類型的,而像Animal里依賴的不能實現創建,所以這個時候就需要用到依賴註入了。
關於依賴註入與控制反轉的關係,我個人的理解是:控制反轉是一種設計思想,而依賴註入則是實現控制反轉思想的方法。
IOC容器一般依賴註入有三種:構造函數註入、方法註入、屬性註入。
那麼我們就來照瓢畫葫蘆,實現一下構造函數註入。一般IOC容器構造函數註入是通過一個特性來識別註入的,如果沒有標記特性則去找構造函數參數個數最多的,我們就按照這個思路來。
首先我們新建一個LInjectionConstructorAttribute類,只需繼承Attribute就行了。
public class LInjectionConstructorAttribute :Attribute { }
然後在剛纔那個Animal構造函數上標記上特性,接下來就開始寫代碼。
/// <summary> /// 根據註冊信息生成實例 /// </summary> /// <typeparam name="IT"></typeparam> /// <returns></returns> public IT Rerolve<IT>() { string key = typeof(IT).FullName; Type type = (Type)ContainerTypeDictionary[key]; return (IT)CreateType(type); }
/// <summary> /// 根據提供的類型創建類型實例並返回 /// </summary> /// <param name="type"></param> /// <returns></returns> private object CreateType(Type type) { var ctorArray = type.GetConstructors(); if (ctorArray.Count(c => c.IsDefined(typeof(LInjectionConstructorAttribute), true)) > 0) { //獲取帶特性標記的構造函數參數 foreach (var cotr in type.GetConstructors().Where(c => c.IsDefined(typeof(LInjectionConstructorAttribute), true))) { var paraArray = cotr.GetParameters();//獲取參數數組 if (paraArray.Length == 0) { return Activator.CreateInstance(type); } List<object> listPara = new List<object>(); foreach (var para in paraArray) { string paraKey = para.ParameterType.FullName;//參數類型名稱 //從字典中取出緩存的目標對象並創建對象 Type paraTargetType = (Type)ContainerTypeDictionary[paraKey]; object oPara = CreateType(paraTargetType);//遞歸 listPara.Add(oPara); } return Activator.CreateInstance(type,listPara.ToArray()); } return Activator.CreateInstance(type); } else { //沒有標記特性則使用參數最多的構造函數 var ctor = ctorArray.OrderByDescending(c => c.GetParameters().Length).FirstOrDefault(); var paraArray = ctor.GetParameters();//獲取參數數組 if (paraArray.Length == 0) { return Activator.CreateInstance(type); } List<object> listPara = new List<object>(); foreach (var para in paraArray) { string paraKey = para.ParameterType.FullName;//參數類型名稱 //從字典中取出緩存的目標對象並創建對象 Type paraTargetType = (Type)ContainerTypeDictionary[paraKey]; object oPara = CreateType(paraTargetType);//遞歸 listPara.Add(oPara); } return Activator.CreateInstance(type, listPara.ToArray()); } }
這裡說下為什麼用到遞歸,在我們項目中使用會有層層依賴的關係。比如我這裡Animal依賴於Dog只有一層依賴,如果Gog又依賴於貓、貓依賴於魚。。。(當然這裡只是打個比方)
因為我們不知道具體有幾層依賴,所以使用了遞歸的方法,直到將所有依賴的對象得到後再創建實例。
然後我們再來測試
Container container = new Container(); container.ResgisterType<IAnimal, Animal>(); container.ResgisterType<IDog, Dog>(); IAnimal animal= container.Rerolve<IAnimal>();
註意,如果測試標記特性的一定不要忘了在構造函數上標記特性,然後我們會發現最終也可以得到animal對象。
然後,創建對象這一塊我們先告一段落。接下來進行生命周期管理。
一般的IOC容器都支持三種類型:Transient每次都得到一個新的對象、Scoped同一個域(或者請求、線程)中使用同一個對象、Singleton整個程式生命周期都使用同一實例對象。
那按照我們以上的代碼怎麼才能實現生命周期管理呢?我是這麼想的:既然創建對象的工作都是由我容器來做了,那麼我們在創建完對象之後能不能像註冊類型一樣將對象保存起來呢?
所以我這裡使用了簡單的字典來存儲對象實例,然後通過判斷使用的哪一種生命周期來返回新的對象或是直接返回字典里的對象。直接改造上面的代碼了:
/// <summary> /// 根據提供的類型創建類型實例並返回 /// </summary> /// <param name="type"></param> /// <returns></returns> private object CreateType(Type type) { var ctorArray = type.GetConstructors(); if (ctorArray.Count(c => c.IsDefined(typeof(LInjectionConstructorAttribute), true)) > 0) { //獲取帶特性標記的構造函數參數 foreach (var cotr in type.GetConstructors().Where(c => c.IsDefined(typeof(LInjectionConstructorAttribute), true))) { var paraArray = cotr.GetParameters();//獲取參數數組 if (paraArray.Length == 0) { //return Activator.CreateInstance(type); return GetSocpe(type); } List<object> listPara = new List<object>(); foreach (var para in paraArray) { string paraKey = para.ParameterType.FullName;//參數類型名稱 //從字典中取出緩存的目標對象並創建對象 Type paraTargetType = (Type)ContainerTypeDictionary[paraKey]; object oPara = CreateType(paraTargetType);//遞歸 listPara.Add(oPara); } //return Activator.CreateInstance(type,listPara.ToArray()); return GetSocpe(type, listPara.ToArray()); } return GetSocpe(type); //return Activator.CreateInstance(type); } else { //沒有標記特性則使用參數最多的構造函數 var ctor = ctorArray.OrderByDescending(c => c.GetParameters().Length).FirstOrDefault(); var paraArray = ctor.GetParameters();//獲取參數數組 if (paraArray.Length == 0) { //return Activator.CreateInstance(type); return GetSocpe(type); } List<object> listPara = new List<object>(); foreach (var para in paraArray) { string paraKey = para.ParameterType.FullName;//參數類型名稱 //從字典中取出緩存的目標對象並創建對象 Type paraTargetType = (Type)ContainerTypeDictionary[paraKey]; object oPara = CreateType(paraTargetType);//遞歸 listPara.Add(oPara); } return GetSocpe(type, listPara.ToArray()); //return Activator.CreateInstance(type, listPara.ToArray()); } } private object GetSocpe(Type type, params object[] listPara) { if (_scopeType == (int)Scope.Singleton) { return GetTypeSingleton(type, listPara); } else if (_scopeType == (int)Scope.Transient) { return GetTypeTransient(type, listPara); } else { return GetTypeScoped(type, listPara); } } #region 生命周期 /// <summary> /// 設置獲取實例對象生命周期為Singleton /// </summary> /// <param name="type"></param> /// <param name="listPara"></param> /// <returns></returns> private object GetTypeSingleton(Type type, params object[] listPara) { if (ContainerExampleDictionary.ContainsKey(type.FullName)) { lock (locker) { if (ContainerExampleDictionary.ContainsKey(type.FullName)) { return ContainerExampleDictionary[type.FullName]; } } } if (listPara.Length == 0) { var Example = Activator.CreateInstance(type); ContainerExampleDictionary.Add(type.FullName, Example); return Example; } else { var Example = Activator.CreateInstance(type, listPara.ToArray()); ContainerExampleDictionary.Add(type.FullName, Example); return Example; } } /// <summary> /// 設置獲取實例對象生命周期為Transient /// </summary> /// <param name="type"></param> /// <param name="listPara"></param> /// <returns></returns> private object GetTypeTransient(Type type, params object[] listPara) { if (listPara.Length == 0) { var Example = Activator.CreateInstance(type); //ContainerExampleDictionary.Add(type.FullName, Example); return Example; } else { var Example = Activator.CreateInstance(type, listPara.ToArray()); //ContainerExampleDictionary.Add(type.FullName, Example); return Example; } } /// <summary> /// 設置獲取實例對象生命周期為Scoped /// </summary> /// <param name="type"></param> /// <param name="listPara"></param> /// <returns></returns> private object GetTypeScoped(Type type, params object[] listPara) { var pid = System.Threading.Thread.CurrentThread.ManagedThreadId; if (ContainerExampleDictionary.ContainsKey(type.FullName + pid)) { lock (locker) { if (ContainerExampleDictionary.ContainsKey(type.FullName + pid)) { return ContainerExampleDictionary[type.FullName + pid]; } } } if (listPara.Length == 0) { var Example = Activator.CreateInstance(type); ContainerExampleDictionary.Add(type.FullName + pid, Example); return Example; } else { var Example = Activator.CreateInstance(type, listPara.ToArray()); ContainerExampleDictionary.Add(type.FullName + pid, Example); return Example; } } #endregion
private static Dictionary<string, object> ContainerExampleDictionary = new Dictionary<string, object>(); private static int _scopeType; private static readonly object locker = new object(); public int scopeType { get { return _scopeType; } set { _scopeType = value; } } public enum Scope { Singleton = 0, Transient = 1, Scoped = 2 }
然後調用的時候先聲明下要使用的聲明周期類型就行啦
Container container = new Container(); container.scopeType = (int)Container.Scope.Singleton; container.ResgisterType<IAnimal, Animal>(); container.ResgisterType<IDog, Dog>(); IAnimal animal= container.Rerolve<IAnimal>();
說下三種生命周期管理的實現:
Transient:則可以直接創建一個實例
Scoped:使用的是同一個線程內使用同一個對象實例,使用var pid = System.Threading.Thread.CurrentThread.ManagedThreadId;獲取線程id來判斷的
Singleton:這種則只需一個單例模式獲取就好了
到這裡就先告一段落了,以上只是一個簡單實現,代碼還有需改進的地方以及可以擴展的功能,歡迎提意見指出錯誤。同時代碼已上傳GigHub,還有不懂的可以參考下代碼。
源碼地址:https://github.com/liangchengxuyuan/IocContainer