IoC原理-使用反射/Emit来实现一个最简单的IoC容器

来源:互联网 发布:酒店业数据 编辑:程序博客网 时间:2024/05/20 11:23

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

输出:do work!

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

8.总结

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

0 0