从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(); Dictionary ConfigDictionary { get; } }
2.定义IoCConfig实现
public class IoCConfig:IIoCConfig { ////// 存放配置的字典对象,KEY是接口类型,VALUE是实现接口的类型 /// private Dictionary _configDictionary=new Dictionary (); /// /// 添加配置 /// /// 接口 ///实现接口的类型 public void AddConfig() { //判断TType是否实现TInterface if (typeof(TInterface).IsAssignableFrom(typeof(TType))) { _configDictionary.Add(typeof(TInterface), typeof(TType)); } else { throw new Exception("类型未实现接口"); } } public Dictionary ConfigDictionary { get { return _configDictionary; } } }
使用一个字典来保存Interface跟Class的对应关系。这里是仿造Ninject的配置方式,使用代码来配置。这种配置方式有个好处就是不会写错,因为有IDE来给你检查拼写错误。不要小看这个好处,当你有上百个注入对象的时候,使用Unity的XML来配置对应关系的时候很容易就会发生拼写错误。这种错误往往还很难发现。
当然这里要实现一个按照XML配置文件来设置对应关系的类也很容易,这里就不实现了。
3.定义IIoCContainer容器接口
public interface IIoCContainer { ////// 根据接口返回对应的实例 /// /// /// TInterface Get (); }
4.使用反射实现IoC容器
public class ReflectionContainer:IIoCContainer { ////// 配置实例 /// private IIoCConfig _config; /// /// 构造函数 /// /// ioc配置 public ReflectionContainer(IIoCConfig config) { _config = config; } /// /// 根据接口获取实例对象 /// /// 接口 ///public TInterface Get () { 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 { ////// 配置实例 /// private IIoCConfig _config; public EmitContainer(IIoCConfig config) { _config = config; } /// /// 获取实例 /// /// 接口 ///public TInterface Get () { 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("未找到对应的类型"); } } /// /// 实例化对象 用EMIT /// /// /// /// 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
Emit的实现是抄自Ninject的实现方式。这里其实就是在手动书写IL。一个简单的书写IL的办法就是先用C#写好代码,然后用Reflector等反编译工具查看生成的IL,然后改成Emit代码。
6.实现IoCContainerManager
public class IoCContainerManager { ////// 容器 /// private static IIoCContainer _container; /// /// 获取IOC容器 /// /// ioc配置 /// 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();//添加配置 //获取容器 IIoCContainer container = IoCContainerManager.GetIoCContainer(config); //根据ITest接口去获取对应的实例 ITest test = container.Get (); test.DoWork(); Console.Read(); } }
输出:
这里手动使用IoC容器去获取对应的实例对象,我们也可以配合特性来使代码更加简单。这里就不实现了。
8.总结
通过这么短短的几行代码。我们实现了一个最最简单的IoC容器。它可以实现构造函数注入(默认无参)。但是这就已经揭示了IoC框架最本质的东西:反射或者EMIT来实例化对象。然后我们可以加上缓存,或者一些策略来控制对象的生命周期,比如是否是单例对象还是每次都生成一个新的对象。
源码