從Unity到Spring.Net,到Ninject,幾年來陸陸續續用過幾個IoC框架。雖然會用,但也沒有一直仔細的研究過IoC實現的過程。最近花了點時間,下了Ninject的源碼,研究了一番,很有收穫。下面我要實現一個最最簡單的IoC容器,以讓跟我同樣的小菜能更好的理解IoC框架的到底爲咱們作了什麼。html
IoC是英文Inversion of Control的縮寫。咱們通常叫它「控制反轉」。IoC技術是用來解決面向對象設計一大原則依賴倒置而出現的技術。能夠更好的實現面向接口編程,來使各個組件之間解耦。編程
.NET IoC容器的通常就是兩種,一是反射,二是使用Emit來直接寫IL。緩存
廢話很少了,想要了解跟多的IoC的知識請Google。框架
先上一張類圖函數
public interface IIoCConfig { void AddConfig<TInterface,TType>(); Dictionary<Type, Type> ConfigDictionary { get; } }
public class IoCConfig:IIoCConfig { /// <summary> /// 存放配置的字典對象,KEY是接口類型,VALUE是實現接口的類型 /// </summary> private Dictionary<Type, Type> _configDictionary=new Dictionary<Type, Type>(); /// <summary> /// 添加配置 /// </summary> /// <typeparam name="TInterface">接口</typeparam> /// <typeparam name="TType">實現接口的類型</typeparam> public void AddConfig<TInterface, TType>() { //判斷TType是否實現TInterface if (typeof(TInterface).IsAssignableFrom(typeof(TType))) { _configDictionary.Add(typeof(TInterface), typeof(TType)); } else { throw new Exception("類型未實現接口"); } } public Dictionary<Type, Type> ConfigDictionary { get { return _configDictionary; } } }
使用一個字典來保存Interface跟Class的對應關係。這裏是仿造Ninject的配置方式,使用代碼來配置。這種配置方式有個好處就是不會寫錯,由於有IDE來給你檢查拼寫錯誤。不要小看這個好處,當你有上百個注入對象的時候,使用Unity的XML來配置對應關係的時候很容易就會發生拼寫錯誤。這種錯誤每每還很難發現。工具
固然這裏要實現一個按照XML配置文件來設置對應關係的類也很容易,這裏就不實現了。ui
public interface IIoCContainer { /// <summary> /// 根據接口返回對應的實例 /// </summary> /// <typeparam name="TInterface"></typeparam> /// <returns></returns> TInterface Get<TInterface>(); }
public class ReflectionContainer:IIoCContainer { /// <summary> /// 配置實例 /// </summary> private IIoCConfig _config; /// <summary> /// 構造函數 /// </summary> /// <param name="config">ioc配置</param> public ReflectionContainer(IIoCConfig config) { _config = config; } /// <summary> /// 根據接口獲取實例對象 /// </summary> /// <typeparam name="TInterface">接口</typeparam> /// <returns></returns> public TInterface Get<TInterface>() { Type type; var can = _config.ConfigDictionary.TryGetValue(typeof(TInterface), out type); if (can) { //反射實例化對象 return (TInterface)Activator.CreateInstance(type); } else { throw new Exception("未找到對應的類型"); } } }
反射這個代碼太簡單了,你們都會用。this
public class EmitContainer:IIoCContainer { /// <summary> /// 配置實例 /// </summary> private IIoCConfig _config; public EmitContainer(IIoCConfig config) { _config = config; } /// <summary> /// 獲取實例 /// </summary> /// <typeparam name="TInterface">接口</typeparam> /// <returns></returns> public TInterface Get<TInterface>() { Type type; var can = _config.ConfigDictionary.TryGetValue(typeof(TInterface), out type); if (can) { BindingFlags defaultFlags = BindingFlags.Public | BindingFlags.Instance; var constructors = type.GetConstructors(defaultFlags);//獲取默認構造函數 var t = (TInterface)this.CreateInstanceByEmit(constructors[0]); return t; } else { throw new Exception("未找到對應的類型"); } } /// <summary> /// 實例化對象 用EMIT /// </summary> /// <typeparam name="T"></typeparam> /// <param name="constructor"></param> /// <returns></returns> private Object CreateInstanceByEmit(ConstructorInfo constructor) { //動態方法 var dynamicMethod = new DynamicMethod(Guid.NewGuid().ToString("N"), typeof(Object), new[] { typeof(object[]) }, true); //方法IL ILGenerator il = dynamicMethod.GetILGenerator(); //實例化命令 il.Emit(OpCodes.Newobj, constructor); //若是是值類型裝箱 if (constructor.ReflectedType.IsValueType) il.Emit(OpCodes.Box, constructor.ReflectedType); //返回 il.Emit(OpCodes.Ret); //用FUNC去關聯方法 var func = (Func<Object>)dynamicMethod.CreateDelegate(typeof(Func<Object>)); //執行方法 return func.Invoke(); } }
Emit的實現是抄自Ninject的實現方式。這裏其實就是在手動書寫IL。一個簡單的書寫IL的辦法就是先用C#寫好代碼,而後用Reflector等反編譯工具查看生成的IL,而後改爲Emit代碼。spa
public class IoCContainerManager { /// <summary> /// 容器 /// </summary> private static IIoCContainer _container; /// <summary> /// 獲取IOC容器 /// </summary> /// <param name="config">ioc配置</param> /// <returns></returns> public static IIoCContainer GetIoCContainer(IIoCConfig config) { if (_container==null) { //反射方式 _container = new ReflectionContainer(config); //EMIT方式 // _container=new EmitContainer(config); } return _container; } }
代碼太簡單,很少說了。
public interface ITest { void DoWork(); } public class Test:ITest { public void DoWork() { Console.WriteLine("do work!"); } } class Program { static void Main(string[] args) { IIoCConfig config = new IoCConfig(); config.AddConfig<ITest, Test>();//添加配置 //獲取容器 IIoCContainer container = IoCContainerManager.GetIoCContainer(config); //根據ITest接口去獲取對應的實例 ITest test = container.Get<ITest>(); test.DoWork(); Console.Read(); } }
這裏手動使用IoC容器去獲取對應的實例對象,咱們也能夠配合特性來使代碼更加簡單。這裏就不實現了。設計
經過這麼短短的幾行代碼。咱們實現了一個最最簡單的IoC容器。它能夠實現構造函數注入(默認無參)。可是這就已經揭示了IoC框架最本質的東西:反射或者EMIT來實例化對象。而後咱們能夠加上緩存,或者一些策略來控制對象的生命週期,好比是不是單例對象仍是每次都生成一個新的對象。