IoC原理-使用反射/Emit來實現一個最簡單的IoC容器

從Unity到Spring.Net,到Ninject,幾年來陸陸續續用過幾個IoC框架。雖然會用,但也沒有一直仔細的研究過IoC實現的過程。最近花了點時間,下了Ninject的源碼,研究了一番,很有收穫。下面我要實現一個最最簡單的IoC容器,以讓跟我同樣的小菜能更好的理解IoC框架的到底爲咱們作了什麼。html

什麼是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配置文件來設置對應關係的類也很容易,這裏就不實現了。ui

 

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("未找到對應的類型");
            }
        }
    }

反射這個代碼太簡單了,你們都會用。this

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代碼。spa

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來實例化對象。而後咱們能夠加上緩存,或者一些策略來控制對象的生命週期,好比是不是單例對象仍是每次都生成一個新的對象。

 源碼

相關文章
相關標籤/搜索