從Unity到Spring.Net,到Ninject,幾年來陸陸續續用過幾個IoC框架。雖然會用,但也沒有一直仔細的研究過IoC實現的過程。最近花了點時間,下了Ninject的源碼,研究了一番,頗有收獲。下面我要實現一個最最簡單的IoC容器,以讓跟我一樣的小菜能更好的理解IoC框架的到底為我們做了什么。
什么是IoC
IoC是英文Inversion of Control的縮寫。我們一般叫它“控制反轉”。IoC技術是用來解決面向對象設計一大原則依賴倒置而出現的技術。可以更好的實現面向接口編程,來使各個組件之間解耦。
IoC的實現原理
.NET IoC容器的一般就是兩種,一是反射,二是使用Emit來直接寫IL。
廢話不多了,想要了解跟多的IoC的知識請Google。
關於實現
先上一張類圖
1.定義IIoCConfig接口
public interface IIoCConfig { void AddConfig<TInterface,TType>(); Dictionary<Type, Type> ConfigDictionary { get; } }
2.定義IoCConfig實現
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配置文件來設置對應關系的類也很容易,這里就不實現了。
3.定義IIoCContainer容器接口
public interface IIoCContainer { /// <summary> /// 根據接口返回對應的實例 /// </summary> /// <typeparam name="TInterface"></typeparam> /// <returns></returns> TInterface Get<TInterface>(); }
4.使用反射實現IoC容器
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("未找到對應的類型"); } } }
反射這個代碼太簡單了,大家都會用。
5.使用Emit實現IoC容器
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代碼。
6.實現IoCContainerManager
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; } }
代碼太簡單,不多說了。
7.使用
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容器去獲取對應的實例對象,我們也可以配合特性來使代碼更加簡單。這里就不實現了。
8.總結
通過這么短短的幾行代碼。我們實現了一個最最簡單的IoC容器。它可以實現構造函數注入(默認無參)。但是這就已經揭示了IoC框架最本質的東西:反射或者EMIT來實例化對象。然后我們可以加上緩存,或者一些策略來控制對象的生命周期,比如是否是單例對象還是每次都生成一個新的對象。