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来实例化对象。然后我们可以加上缓存,或者一些策略来控制对象的生命周期,比如是否是单例对象还是每次都生成一个新的对象。

 源码

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏技术专栏

慕课网Flask高级编程实战-11.Python与Flask的结合应用

视图函数接受用户填写的email账号,如果不存在应该跳转到404界面,这个逻辑flask-sqlalchemy为我们提供了良好的封装,不需要手动去处理,只需要调...

1473
来自专栏coding...

iOS 多国语言本地化与App内语言切换(Swift)写在前面前言准备工作storyboard/xib本地化纯代码本地化语言切换后记

本文同步 个人博客 简书 掘金 慕课 使用Xcode 9.3 Swift4.1

1193
来自专栏Django中文社区

Django模板标签regroup的妙用

在使用 Django 开发时,有时候我们需要在模板中按对象的某个属性分组显示一系列数据。例如博客文章按照时间归档分组显示文章列表(示例效果请看我的博客的归档页面...

2806
来自专栏yukong的小专栏

【SpringBoot2.0系列11】SpringBoot之@Elasticsearch完成CURD

【SpringBoot系列02】SpringBoot之使用Thymeleaf视图模板

1932
来自专栏祝威廉

ElasticSearch Bulk 源码解析

读这篇文章前,建议先看看ElasticSearch Rest/RPC 接口解析,有利于你把握ElasticSearch接受处理请求的脉络。对于RPC类的调用,我...

834
来自专栏屈定‘s Blog

由需求而产生的一款db导出excel的工具

程序员最大的毛病可能就是懒,因为懒所以做出了许许多多提高自己工作效率的工具. 起因于我是商业开发,既然是商业项目避免不了各种数据统计,虽然公司有专门的数据平台,...

1075

访问数据 - 反应方式(Vert.x入门的第4部分)

原文地址:https://dzone.com/articles/accessing-data-the-reactive-way

5894
来自专栏零基础使用Django2.0.1打造在线教育网站

在线网站搭建(七):数据库字段的定义(上)

努力与运动兼备~~~有任何问题可以加我好友或者关注微信公众号,欢迎交流,我们一起进步!

1062
来自专栏JetpropelledSnake

SQL学习笔记之MySQL中真假“utf8” 问题

最近我遇到了一个 bug,我试着通过 Rails 在以“utf8”编码的 MariaDB 中保存一个 UTF-8 字符串,然后出现了一个离奇的错误:

502
来自专栏MasiMaro 的技术博文

windows 多任务与进程

多任务的本质就是并行计算,它能够利用至少2处理器相互协调,同时计算同一个任务的不同部分,从而提高求解速度,或者求解单机无法求解的大规模问题。以前的分布式计算正是...

834

扫码关注云+社区