Dependency Container своими руками

ru-RU | создано: 17.04.2020 | опубликовано: 17.04.2020 | обновлено: 26.05.2020 | просмотров за всё время: 241

А вы никогда не задумывались как работает Dependency Container?

Тема статьи

Как работает DependencyInjection контейнер? Что внутри этого "черного" ящика? Из чего он состоит? Зачем нужна регистрация?

Простой ответ

Все контейнеры работают одинаково! Принцип прост как "дважды два": Прежде чем взять что-то из коробочки - положи туда то, что тебе потом потребуется.

Разница лишь в том, какие и сколько полезных "фишечек" создадут для тебя разработчики контейнера. Не скрою, что когда-то и я изобретал свой велосипед:

И на тот момент я думал, что это самый лучший контейнер, потому что самый удобный, "родной" и полезный. Но время шло, и появлялись контейнеры гораздо более эффективные. Времени на развитие своего контейнера стало критически не хватать, и я забросил его.

Но тем не менее простота его использования - это большой плюс. Более того, исходники остались, и я хочу с вами ими поделиться:

namespace Calabonga.SimpleIoc
{
    public class SimpleIoC
    {
        #region public methods

        public SimpleIoC()
        {
            RegisterInstance(this);
        }

        public void Register<TType>() where TType : class
        {
            Register<TType, TType>(false, null);
        }

        public void Register<TType, TLive>() where TLive : class, TType
        {
            Register<TType, TLive>(false, null);
        }

        public void RegisterSingleton<TType>() where TType : class
        {
            RegisterSingleton<TType, TType>();
        }

        public void RegisterSingleton<TType, TLive>() where TLive : class, TType
        {
            Register<TType, TLive>(true, null);
        }

        public void RegisterInstance<TType>(TType instance) where TType : class
        {
            RegisterInstance<TType, TType>(instance);
        }

        public void RegisterInstance<TType, TLive>(TLive instance) where TLive : class, TType
        {
            Register<TType, TLive>(true, instance);
        }

        public TResolve Resolve<TResolve>()
        {
            return (TResolve)ResolveObject(typeof(TResolve));
        }

        public object Resolve(Type type)
        {
            return ResolveObject(type);
        }
        #endregion public methods

        #region private methods
        private void Register<TType, TLive>(bool isSingleton, TLive instance)
        {
            var type = typeof(TType);
            if (_registeredObjects.ContainsKey(type)) _registeredObjects.Remove(type);
            _registeredObjects.Add(type, new EnteredObject(typeof(TLive), isSingleton, instance));
        }
        private object ResolveObject(Type type)
        {
            var registeredObject = _registeredObjects[type];
            if (registeredObject == null)
            {
                throw new ArgumentOutOfRangeException(
                $"The type {type.Name} has not been registered");
            }
            return GetInstance(registeredObject);
        }
        private object GetInstance(EnteredObject registeredObject)
        {
            var instance = registeredObject.SingletonInstance;
            if (instance != null) return instance;
            var parameters = ResolveConstructorParameters(registeredObject);
            instance = registeredObject.CreateInstance(parameters.ToArray());
            return instance;
        }
        private IEnumerable<object> ResolveConstructorParameters(EnteredObject registeredObject)
        {
            var constructorInfo = registeredObject.LiveType.GetConstructors().First();
            return constructorInfo.GetParameters().Select(parameter => ResolveObject(parameter.ParameterType));
        }

        private class EnteredObject
        {
            private readonly bool _isSingleton;
            public EnteredObject(Type liveType, bool isSingleton, object instance)
            {
                _isSingleton = isSingleton;
                LiveType = liveType;
                SingletonInstance = instance;
            }
            public Type LiveType
            {
                get;
            }
            public object SingletonInstance
            {
                get;
                private set;
            }

            public object CreateInstance(params object[] args)
            {
                var instance = Activator.CreateInstance(LiveType, args);
                if (_isSingleton)
                    SingletonInstance = instance;
                return instance;
            }
        }
        #endregion private methods

        #region MyRegion
        private readonly IDictionary<Type, EnteredObject> _registeredObjects = new Dictionary<Type, EnteredObject>();
        #endregion
    }
}

Вот всё из чего состоит контейнер. Регистрация объектов и создание их при разрешении (resolve) через Activator.

Ссылки

SimpleIoc nuget-пакет

Видео по теме