从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来实例化对象。而后咱们能够加上缓存,或者一些策略来控制对象的生命周期,好比是不是单例对象仍是每次都生成一个新的对象。