一步一步造個Ioc輪子目錄
一步一步造個IoC輪子(一):Ioc是什麼一步一步造個IoC輪子(二):詳解泛型工廠一步一步造個IoC輪子(三):構造基本的IoC容器首先,我們來畫個大餅,定義好構造函數,注冊函數及獲取函數這幾個最基本的使用方法
/// <summary> /// IoC容器 /// </summary> public class Container { /// <summary> /// 構造函數 /// </summary> /// <param name="cfg">配置文件,默認為啟動目錄下"cfg.xml"</param> public Container(string cfg = "cfg.xml") { } /// <summary> /// 注冊 /// </summary> /// <typeparam name="F">接口或父類</typeparam> /// <typeparam name="S">繼承類</typeparam> /// <param name="name">索引名稱,默認為空</param> public void Register<F, S>(string name = null) where S : F, new() where F : class { } /// <summary> /// 注冊單例 /// </summary> /// <typeparam name="F">接口或父類</typeparam> /// <typeparam name="S">繼承類</typeparam> /// <param name="name"></param> /// <param name="name">索引名稱,默認為空</param> public void RegisterSingleton<F, S>(string name = null) where S : F, new() where F : class { } /// <summary> /// 注冊,對象由傳入的Func委托創建 /// </summary> /// <typeparam name="T">接口或父類</typeparam> /// <param name="func">對象創建委托</param> /// <param name="name">索引名稱,默認為空</param> public void Register<T>(Func<T> func, string name = null) where T : class { } /// <summary> /// 注冊單例,對象由傳入的Func委托創建 /// </summary> /// <typeparam name="T">接口或父類</typeparam> /// <param name="func">對象創建委托</param> /// <param name="name">索引名稱,默認為空</param> public void RegisterSingleton<T>(Func<T> func, string name = null) where T : class { } /// <summary> /// 獲取 /// </summary> /// <typeparam name="T">接口或父類</typeparam> /// <returns>注冊的繼承類</returns> public T Resolve<T>() where T : class { throw new NotImplementedException(); } /// <summary> /// 獲取 /// </summary> /// <typeparam name="T">接口或父類</typeparam> /// <param name="name">索引名稱</param> /// <returns>注冊的繼承類</returns> public T Resolve<T>(string name) where T : class { throw new NotImplementedException(); } /// <summary> /// 取出當前所有注冊的列表 /// </summary> /// <typeparam name="T">接口或父類</typeparam> /// <returns>索引名稱列表,null表示無索引注冊</returns> public IList<string> GetRegisterList<T>() where T : class { throw new NotImplementedException(); } }
接下來我們把上一篇魔改過的泛型工廠再魔改一下,我們把這個工廠去掉static再添加支持泛型委托創建對象的注冊方法,由於整個Ioc設計不是靜態使用的,所以工廠裡的內部類static readonly魔法要退化回雙檢鎖了:(
當然在不使用索引的情況下我們還是可以保留一個魔法單例的_(:з」∠)_
internal class Factory<T> where T : class { #region 空間換性能 private static readonly Factory<T> instance0 = new Factory<T>(); private static readonly Factory<T> instance1 = new Factory<T>(); private static readonly ConcurrentDictionary<int, Factory<T>> instances = new ConcurrentDictionary<int, Factory<T>>(); private static Func<int, Factory<T>> newFunc = (cid) => { return new Factory<T>(); }; public static Factory<T> GetFactory(int id) { if (id == 0) return instance0; if (id == 1) return instance1; return instances.GetOrAdd(id, newFunc); } #endregion #region Creaters interface ICreater { T Create(); } class Creater<U> : ICreater where U : T, new() { public T Create() { return new U(); } } class FuncCreater : ICreater { Func<T> func; public FuncCreater(Func<T> func) { this.func = func; } public T Create() { return func(); } } class MagicSingletonCreater<U> : ICreater where U : T, new() { class InstanceClass { public static readonly T Instance = new U(); } public T Create() { return InstanceClass.Instance; } } class SingletonCreater<U> : ICreater where U : T, new() { //由於整個IoC容器不是靜態的,所以不能用內部類static readonly魔法來搞,否則可能會出現多個索引名稱注冊了單例子,但引用了同一個對象,多個索引名稱變成了別名的情況,只能用雙檢鎖了 object locker = new object(); T instance; public T Create() { if (instance == null) { lock (locker) { if (instance == null) { Interlocked.Exchange(ref instance, new U()); } } } return instance; } } class FuncSingletonCreater : ICreater { Func<T> func; public FuncSingletonCreater(Func<T> func) { this.func = func; } //由於整個IoC容器不是靜態的,所以不能用內部類static readonly魔法來搞,否則可能會出現多個索引名稱注冊了單例子,但引用了同一個對象,多個索引名稱變成了別名的情況,只能用雙檢鎖了 private object locker = new object(); private T instance; public T Create() { if (instance == null) { lock (locker) { if (instance == null) { Interlocked.Exchange(ref instance, func()); } } } return instance; } } class MagicFuncSingletonCreater<S> : ICreater where S : T { static Func<S> magicFunc; public MagicFuncSingletonCreater(Func<S> func) { magicFunc = func; } class InstanceClass { public static readonly S Instance = magicFunc(); } public T Create() { return InstanceClass.Instance; } } #endregion ConcurrentBag<string> regs = new ConcurrentBag<string>(); public IList<string> GetRegisterList() { return regs.ToList(); } private void AddReg(string name) { if (regs.Contains(name)) return; regs.Add(name); } #region 無索引的 private ICreater creater; public T Get() { return creater.Create(); } public void Reg<S>() where S : T, new() { creater = new Creater<S>(); AddReg(null); } public void RegSingleton<S>() where S : T, new() { creater = new MagicSingletonCreater<S>(); AddReg(null); } public void Reg(Func<T> func) { creater = new FuncCreater(func); AddReg(null); } public void RegSingleton<S>(Func<S> func) where S : T { creater = new MagicFuncSingletonCreater<S>(func); AddReg(null); } #endregion #region 有索引的 private IDictionary<string, ICreater> creaters = new ConcurrentDictionary<string, ICreater>(); public T Get(string key) { ICreater ct; if (creaters.TryGetValue(key, out ct)) return ct.Create(); throw new Exception("未注冊"); } public void Reg<S>(string key) where S : T, new() { creaters[key] = new Creater<S>(); AddReg(key); } public void RegSingleton<S>(string key) where S : T, new() { creaters[key] = new SingletonCreater<S>(); AddReg(key); } public void Reg(Func<T> func, string key) { creaters[key] = new FuncCreater(func); AddReg(key); } public void RegSingleton(Func<T> func, string key) { creaters[key] = new FuncSingletonCreater(func); AddReg(key); } #endregion }
好了,有了魔法工廠,IoC容器嘛,不就代理一下這個魔法工廠的操作,來來來,接下來折騰這容器
/// <summary> /// IoC容器 /// </summary> public class Container { private static volatile int currCid = -1; private int cid; /// <summary> /// 構造函數 /// </summary> /// <param name="cfg">配置文件,默認為啟動目錄下"cfg.xml"</param> public Container(string cfg = "cfg.xml") { cid = Interlocked.Increment(ref currCid); } /// <summary> /// 注冊 /// </summary> /// <typeparam name="F">接口或父類</typeparam> /// <typeparam name="S">繼承類</typeparam> /// <param name="name">索引名稱,默認為空</param> public void Register<F, S>(string name = null) where S : F, new() where F : class { if (name == null) Factory<F>.GetFactory(cid).Reg<S>(); else Factory<F>.GetFactory(cid).Reg<S>(name); } /// <summary> /// 注冊單例 /// </summary> /// <typeparam name="F">接口或父類</typeparam> /// <typeparam name="S">繼承類</typeparam> /// <param name="name"></param> /// <param name="name">索引名稱,默認為空</param> public void RegisterSingleton<F, S>(string name = null) where S : F, new() where F : class { if (name == null) Factory<F>.GetFactory(cid).RegSingleton<S>(); else Factory<F>.GetFactory(cid).RegSingleton<S>(name); } /// <summary> /// 注冊,對象由傳入的Func委托創建 /// </summary> /// <typeparam name="F">接口或父類</typeparam> /// <param name="func">對象創建委托</param> /// <param name="name">索引名稱,默認為空</param> public void Register<F>(Func<F> func, string name = null) where F : class { if (name == null) Factory<F>.GetFactory(cid).Reg(func); else Factory<F>.GetFactory(cid).Reg(func, name); } /// <summary> /// 注冊單例,對象由傳入的Func委托創建 /// </summary> /// <typeparam name="F">接口或父類</typeparam> /// <param name="func">對象創建委托</param> /// <param name="name">索引名稱,默認為空</param> public void RegisterSingleton<F>(Func<F> func, string name = null) where F : class { if (name == null) Factory<F>.GetFactory(cid).RegSingleton(func); else Factory<F>.GetFactory(cid).RegSingleton(func, name); } /// <summary> /// 獲取 /// </summary> /// <typeparam name="F">接口或父類</typeparam> /// <returns>注冊的繼承類</returns> public F Resolve<F>() where F : class { return Factory<F>.GetFactory(cid).Get(); } /// <summary> /// 獲取 /// </summary> /// <typeparam name="F">接口或父類</typeparam> /// <param name="name">索引名稱</param> /// <returns>注冊的繼承類</returns> public F Resolve<F>(string name) where F : class { return Factory<F>.GetFactory(cid).Get(name); } /// <summary> /// 取出當前所有注冊的列表 /// </summary> /// <typeparam name="F">接口或父類</typeparam> /// <returns>索引名稱列表,null表示無索引注冊</returns> public IList<string> GetRegisterList<F>() where F : class { return Factory<F>.GetFactory(cid).GetRegisterList(); } }
基本的IoC容器已經完成,讀取配置的方法我們下一篇再處理,先來點測試吧,看看這個魔法IoC能不能用,性能如何
public static void Main(string[] args) { Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); var ctx = new Container(); ctx.RegisterSingleton<ISMS, XSMS>(); ctx.Register<ISMS, FriendSMS>("fsms"); var cs = ctx.GetRegisterList<ISMS>(); foreach (var c in cs) { //Console.WriteLine("ctx ISMS注冊:" + c); } Console.WriteLine("請輸入循環次數"); int max = int.Parse(Console.ReadLine()); var sw = new Stopwatch(); sw.Start(); for (var i = 0; i < max; i++) { var x = ctx.Resolve<ISMS>(); x.Send(null, 0, null, null); } sw.Stop(); Console.WriteLine("IoC單例耗時{0}ms,平均每次{1}ns", sw.ElapsedMilliseconds, sw.ElapsedMilliseconds * 1000000M / (decimal)max); var ctx2 = new Container(); ctx2.Register<ISMS, AlidayuSMS>(); ctx2.RegisterSingleton<ISMS, XSMS>("fsms"); sw.Restart(); for (var i = 0; i < max; i++) { var x = ctx2.Resolve<ISMS>(); x.Send(null, 0, null, null); } sw.Stop(); Console.WriteLine("IoC創建耗時{0}ms,平均每次{1}ns", sw.ElapsedMilliseconds, sw.ElapsedMilliseconds * 1000000M / (decimal)max); sw.Restart(); for (var i = 0; i < max; i++) { var x = new XSMS(); x.Send(null, 0, null, null); } sw.Stop(); Console.WriteLine("直接創建耗時{0}ms,平均每次{1}ns", sw.ElapsedMilliseconds, sw.ElapsedMilliseconds * 1000000M / (decimal)max); Console.ReadLine(); }
來看看試試1000萬次結果吧
請輸入循環次數
10000000
IoC單例耗時181ms,平均每次18.1ns
IoC創建耗時815ms,平均每次81.5ns
直接創建耗時41ms,平均每次4.1ns
VS2015 Release模式下VS直接運行的結果
改用CMD直接運行
幾乎一樣的速度
改為名稱索引的速度如下
比無索引的慢那麼一點點,字典的速度最不是蓋的,到最後篇我們再看能不能用EMIT織一個類似switch的優化方案,比如參數數量在5以下用if判斷,5以上改為更好的普通字典(不是Concurrent那個)
好了這個基本的IoC容器,速度嘛,真泛型魔法加持下,無與倫比,最後一篇優化再出一個靜態的版本,速度只會更高:)
好了,裝逼裝到這裡該發代碼了,注冊一只GitHub上傳之,Noone