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来实例化对象。然后我们可以加上缓存,或者一些策略来控制对象的生命周期,比如是否是单例对象还是每次都生成一个新的对象。
相关文章
- C++-STL-组件(一)-容器01:string【深浅拷贝、模拟实现、写时拷贝】
- C++-STL-组件(一)-容器06:queue(队列)
- java容器HashMap原理
- Docker公司宣布将重点关注容器安全
- Java 容器的基本概念
- Red Hat Summit 2017:面向容器、云和安全的5大新品
- 万能日志数据收集器 Fluentd - 每天5分钟玩转 Docker 容器技术(91)
- Kubernetes容器集群管理环境 - 完整部署(下篇)
- 容器之后的下一波浪潮?Amazon CTO谈无服务器计算
- td内容自动换行 ,td超过宽度显示点点点… , td 使用 overflow:hidden 无效,英文 数字 不换行 撑破div容器
- Docker容器间网络互联原理,讲不明白算我输.... (一)
- vector容器经常用法
- 阿里云加大客户自有环境的容器服务支持
- IT生产环境中容器编排系统的五个最佳做法
- 安卓3D旋转容器_实现任意视图3D旋转
- STL容器的迭代器失效的原因
- 持久化存储对容器来说真的适合吗?
- VS2008增加ActiveX控件测试容器
- 使用Docker的macvlan为容器提供桥接网络及跨主机通讯
- 【温故而知新】C和C++9:STL中的set容器
- 容器内部文件调用