IoC原理-使用反射/Emit来实现一个最简单的IoC容器
从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来实例化对象。然后我们可以加上缓存,或者一些策略来控制对象的生命周期,比如是否是单例对象还是每次都生成一个新的对象。
QQ群:1022985150 VX:kklldog 一起探讨学习.NET技术
作者:Agile.Zhou(kklldog)
出处:http://www.cnblogs.com/kklldog/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。