zoukankan      html  css  js  c++  java
  • 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。

    关于实现

    先上一张类图

    image

    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();
            }
        }

    输出:

    image

    这里手动使用IoC容器去获取对应的实例对象,我们也可以配合特性来使代码更加简单。这里就不实现了。

    8.总结

    通过这么短短的几行代码。我们实现了一个最最简单的IoC容器。它可以实现构造函数注入(默认无参)。但是这就已经揭示了IoC框架最本质的东西:反射或者EMIT来实例化对象。然后我们可以加上缓存,或者一些策略来控制对象的生命周期,比如是否是单例对象还是每次都生成一个新的对象。

     源码

  • 相关阅读:
    winform中利用正则表达式得到有效的电话/手机号
    winform运行时如何接受参数?(示例)
    [基础]Javascript中的继承示例代码
    [转]C#中"is" vs "as"
    Javascript数组常用方法[包含MS AJAX.NET的prototype扩展方法]示例
    linq学习笔记(一)
    用winform应用程序登录网站的解决方案
    [转贴]操纵自如--页面内的配合与通信
    .net3.0中的扩展方法(示例)
    window.location或window.open如何指定target?
  • 原文地址:https://www.cnblogs.com/kklldog/p/3395641.html
Copyright © 2011-2022 走看看