C#通过MinIoc源码带你了解Ioc容器实现

背景

IOC容器是构建一个应用程序非常重要的组成部分,在很多的IoC-Invertion of Control,即控制反转,是一种程序设计思想,在彻底了解Ioc容器之前我们先要来理解一些重要的一些概念,有了这些概念你才能对整个Ioc容器有一个非常清晰地认知。

先初步了解几个概念:

  • 依赖(Dependency):表示一个类依赖于另一个类,类之间彼此相互联系。

  • 依赖倒置原则(DIP):设计模式六大原则之一,是一种软件架构设计原则。

  • 控制反转(IoC):一种软件设计原则,上层对下层的依赖(即底层模块的获得)交给第三方。

  • 依赖注入(DI):实现IoC的一种方式、手段。

IoC容器:依赖注入的框架,用来映射依赖,管理对象的创建和生存周期, IoC容器是一个DI框架,对于IOC容器主要功能有一下几点:

  • A.动态创建、注入依赖对象;

  • B.管理对象生命周期;

  • C.映射依赖关系;

常见的IoC容器:Spring.NET,Castle Windsor, Ninject,Autofac,Unity......今天通过Microsoft的一个开源MinIoc的项目来带你彻底了解Ioc容器的实现细节

实现

在了解这个MinIoc之前,我们需要认真理解其内部的实现,所以我们首先来看看具体的源码以及单元测试代码

  1. 接口定义

这个是我们首先来重点关注的内容,在这个MinIoc主要定义了三个接口IScopeIRegisteredType以及ILifetime,第一个是整个Container本身实现的接口,这个接口是从IDisposable, IServiceProvider这两个接口继承而来的,这个IDisposable接口就不用赘述,主要是用于一些对象的释放,第二个IServiceProvider接口里面主要是通过传入Type来获取最终的容器中的对象,我们先来看一下简单代码实现。

        /// <summary>
        /// Returns the object registered for the given type, if registered
        /// </summary>
        /// <param name="type">Type as registered with the container</param>
        /// <returns>Instance of the registered type, if registered; otherwise <see langword="null"/></returns>
        public object GetService(Type type)
        {
            Func<ILifetime, object> registeredType;

            if (!_registeredTypes.TryGetValue(type, out registeredType))
            {
                return null;
            }

            return registeredType(_lifetime);
        }

这段代码主要是从定义的成员变量中获取当前传入的type对应的Func<ILifetime,object>委托,然后根据成员变量_lifetime来获取最终的object返回值,这个里面两个成员变量定义如下:

        // Map of registered types
        private readonly Dictionary<Type, Func<ILifetime, object>> _registeredTypes = new Dictionary<Type, Func<ILifetime, object>>();

        // Lifetime management
        private readonly ContainerLifetime _lifetime;

这里我们重点来关注第三个接口ILifetime的定义

// ILifetime management adds resolution strategies to an IScope
        interface ILifetime : IScope
        {
            object GetServiceAsSingleton(Type type, Func<ILifetime, object> factory);

            object GetServicePerScope(Type type, Func<ILifetime, object> factory);
        }

这个接口的定义中有两个重要方法,这两个方法是用于获取当前对象的生命周期内的重点实现,在后面我们将会对继承自这个接口的重点类型进行分析和讲解。

  1. 重点接口实现

这个部分我们从上面定义的ILifetime接口的实现来说起,我们先来看看第一个ContainerLifetime,我们首先来看看这个继承自ILifetime接口的实现

 // Container lifetime management
        class ContainerLifetime : ObjectCache, ILifetime
        {
            // Retrieves the factory functino from the given type, provided by owning container
            public Func<Type, Func<ILifetime, object>> GetFactory { get; private set; }

            public ContainerLifetime(Func<Type, Func<ILifetime, object>> getFactory) => GetFactory = getFactory;

            public object GetService(Type type) => GetFactory(type)(this);

            // Singletons get cached per container
            public object GetServiceAsSingleton(Type type, Func<ILifetime, object> factory)
                => GetCached(type, factory, this);

            // At container level, per-scope items are equivalent to singletons
            public object GetServicePerScope(Type type, Func<ILifetime, object> factory)
                => GetServiceAsSingleton(type, factory);
        }

这个ContainerLifetime从一个抽象的ObjectCache继承而来,顾名思义就是缓存当前对象并且是线程安全的

// ObjectCache provides common caching logic for lifetimes
        abstract class ObjectCache
        {
            // Instance cache
            private readonly ConcurrentDictionary<Type, object> _instanceCache = new ConcurrentDictionary<Type, object>();

            // Get from cache or create and cache object
            protected object GetCached(Type type, Func<ILifetime, object> factory, ILifetime lifetime)
                => _instanceCache.GetOrAdd(type, _ => factory(lifetime));

            public void Dispose()
            {
                foreach (var obj in _instanceCache.Values)
                    (obj as IDisposable)?.Dispose();
            }
        }

这个抽象类里面定义了一个Dispose方法用于一次性释放集合中所有实现了IDisposeable接口的对象,另外这个类里面定义了一个protected的GetCached方法,这个方法有三个参数,第一个是类型Type,第二个是Func类型委托,第三个就是ILifetime接口,这个方法顾名思义就是从当前线程安全的字典中获取Type对应的对象没有获取到就添加进去,只不过这个对象和ILifetime密切相关。
接下来我们再从另外一个继承自ILifetime接口的对象ScopeLifetime来说去,我们先来看看具体的代码。

// Per-scope lifetime management
        class ScopeLifetime : ObjectCache, ILifetime
        {
            // Singletons come from parent container‘s lifetime
            private readonly ContainerLifetime _parentLifetime;

            public ScopeLifetime(ContainerLifetime parentContainer) => _parentLifetime = parentContainer;

            public object GetService(Type type) => _parentLifetime.GetFactory(type)(this);

            // Singleton resolution is delegated to parent lifetime
            public object GetServiceAsSingleton(Type type, Func<ILifetime, object> factory)
                => _parentLifetime.GetServiceAsSingleton(type, factory);

            // Per-scope objects get cached
            public object GetServicePerScope(Type type, Func<ILifetime, object> factory)
                => GetCached(type, factory, this);
        }

这个类的区别是构造函数中传入的是一个ContainerLifetime作为一个ParentLifetime对象,在调用GetServiceAsSingleton方法的时候是调用parentLifttime对应的对象这个需要注意,另外GetServicePerScope是获取当前ScopeLifetime范围内的缓存在基类中的object对象,这个在使用的时候需要特别注意。

  1. 注册过程

我们首先通过一个简单的实例来一步步讲述整个注册的过程,后面会通过一个复杂一些的例子来进一步讲解整个过程

[TestMethod]
        public void RecursiveReflectionConstruction()
        {
            Container.Register<IFoo>(typeof(Foo));
            Container.Register<IBar>(typeof(Bar));
            Container.Register<IBaz>(typeof(Baz));

            IBaz instance = Container.Resolve<IBaz>();

            // Test that the correct types were created
            Assert.IsInstanceOfType(instance, typeof(Baz));

            var baz = instance as Baz;
            Assert.IsInstanceOfType(baz.Bar, typeof(Bar));
            Assert.IsInstanceOfType(baz.Foo, typeof(Foo));
        }

在看这段代码之前我们先来看看我们定义的这几个类和接口

 #region Types used for tests
        interface IFoo
        {
        }

        class Foo : IFoo
        {
        }

        interface IBar
        {
        }

        class Bar : IBar
        {
            public IFoo Foo { get; set; }

            public Bar(IFoo foo)
            {
                Foo = foo;
            }
        }

        interface IBaz
        {
        }

        class Baz : IBaz
        {
            public IFoo Foo { get; set; }
            public IBar Bar { get; set; }

            public Baz(IFoo foo, IBar bar)
            {
                Foo = foo;
                Bar = bar;
            }
        }

        class SpyDisposable : IDisposable
        {
            public bool Disposed { get; private set; }

            public void Dispose() => Disposed = true;
        }
        #endregion

我们来一步步分析这个Register方法,其实这一组注册的方法是一组扩展方法,我们来看看这个定义,这种写代码的方式很值得我们去学习,即将重要的注册和解析的普通以及各种泛型方法放在扩展方法中,这样接口更加清晰而且更加容易理解。

/// <summary>
    /// Extension methods for Container
    /// </summary>
    static class ContainerExtensions
    {
        /// <summary>
        /// Registers an implementation type for the specified interface
        /// </summary>
        /// <typeparam name="T">Interface to register</typeparam>
        /// <param name="container">This container instance</param>
        /// <param name="type">Implementing type</param>
        /// <returns>IRegisteredType object</returns>
        public static Container.IRegisteredType Register<T>(this Container container, Type type)
            => container.Register(typeof(T), type);

        /// <summary>
        /// Registers an implementation type for the specified interface
        /// </summary>
        /// <typeparam name="TInterface">Interface to register</typeparam>
        /// <typeparam name="TImplementation">Implementing type</typeparam>
        /// <param name="container">This container instance</param>
        /// <returns>IRegisteredType object</returns>
        public static Container.IRegisteredType Register<TInterface, TImplementation>(this Container container)
            where TImplementation : TInterface
            => container.Register(typeof(TInterface), typeof(TImplementation));

        /// <summary>
        /// Registers a factory function which will be called to resolve the specified interface
        /// </summary>
        /// <typeparam name="T">Interface to register</typeparam>
        /// <param name="container">This container instance</param>
        /// <param name="factory">Factory method</param>
        /// <returns>IRegisteredType object</returns>
        public static Container.IRegisteredType Register<T>(this Container container, Func<T> factory)
            => container.Register(typeof(T), () => factory());

        /// <summary>
        /// Registers a type
        /// </summary>
        /// <param name="container">This container instance</param>
        /// <typeparam name="T">Type to register</typeparam>
        /// <returns>IRegisteredType object</returns>
        public static Container.IRegisteredType Register<T>(this Container container)
            => container.Register(typeof(T), typeof(T));

        /// <summary>
        /// Returns an implementation of the specified interface
        /// </summary>
        /// <typeparam name="T">Interface type</typeparam>
        /// <param name="scope">This scope instance</param>
        /// <returns>Object implementing the interface</returns>
        public static T Resolve<T>(this Container.IScope scope) => (T)scope.GetService(typeof(T));
    }

这个里面是调用Container的公开实例方法Register方法并返回一个IRegisteredType接口对象,我们来看看这个方法的实现

 /// <summary>
        /// Registers an implementation type for the specified interface
        /// </summary>
        /// <param name="interface">Interface to register</param>
        /// <param name="implementation">Implementing type</param>
        /// <returns></returns>
        public IRegisteredType Register(Type @interface, Type implementation)
            => RegisterType(@interface, FactoryFromType(implementation));

        private IRegisteredType RegisterType(Type itemType, Func<ILifetime, object> factory)
            => new RegisteredType(itemType, f => _registeredTypes[itemType] = f, factory);

该方法的参数传入一个具体的接口和实现这个接口的类型,这个方法里面用了一个FactoryFromType(implementation)子方法,这个子方法传入具体的接口实现类,我们来看看这段代码的实现

 // Compiles a lambda that calls the given type‘s first constructor resolving arguments
        private static Func<ILifetime, object> FactoryFromType(Type itemType)
        {
            // Get first constructor for the type
            var constructors = itemType.GetConstructors();
            if (constructors.Length == 0)
            {
                // If no public constructor found, search for an internal constructor
                constructors = itemType.GetConstructors(BindingFlags.Instance | BindingFlags.NonPublic);
            }
            var constructor = constructors.First();

            // Compile constructor call as a lambda expression
            var arg = Expression.Parameter(typeof(ILifetime));
            return (Func<ILifetime, object>)Expression.Lambda(
                Expression.New(constructor, constructor.GetParameters().Select(
                    param =>
                    {
                        var resolve = new Func<ILifetime, object>(
                            lifetime => lifetime.GetService(param.ParameterType));
                        return Expression.Convert(
                            Expression.Call(Expression.Constant(resolve.Target), resolve.Method, arg),
                            param.ParameterType);
                    })),
                arg).Compile();
        }

这个方法非常关键,这里定义的是一个私有的静态方法,这个方法首先通过反射获取传入的itemType的构造函数,然后遍历这些参数构建一个完整的表达式树,我们以传入的Baz对象为例,我们获取其参数列表分别获取到IFoo接口和IBar接口,然后遍历这些参数构建一个输入ILifetime接口返回实现类实例的Func类型委托,这个过程非常重要。
接下来通过传入的接口类型以及实现类通过FactoryFromType(implementation)子方法获取的一个Func<ILifetime,object>委托构建一个RegisteredType的实例,我们来看看这个类的定义

// RegisteredType is supposed to be a short lived object tying an item to its container
        // and allowing users to mark it as a singleton or per-scope item
        class RegisteredType : IRegisteredType
        {
            private readonly Type _itemType;
            private readonly Action<Func<ILifetime, object>> _registerFactory;
            private readonly Func<ILifetime, object> _factory;

            public RegisteredType(Type itemType, Action<Func<ILifetime, object>> registerFactory, Func<ILifetime, object> factory)
            {
                _itemType = itemType;
                _registerFactory = registerFactory;
                _factory = factory;

                registerFactory(_factory);
            }

            public void AsSingleton()
                => _registerFactory(lifetime => lifetime.GetServiceAsSingleton(_itemType, _factory));

            public void PerScope()
                => _registerFactory(lifetime => lifetime.GetServicePerScope(_itemType, _factory));           
        }

这个里面最重要的是其构造函数,我们来看看创建这个实例时候传入的参数,其中第二个参数是这样一个表达式 f => _registeredTypes[itemType] = f,这个_registeredTypes是我们在Container中定义的一个Dictionary,这个字典存放的是要注册的接口类型以一个ILifetime为输入的泛型委托,当创建一个新的RegisteredType的时候在构造函数中会默认将当前的接口类型和这个Func委托添加到集合中作为一个默认的键值对,这个键值对的Key是我们之前想要注册的接口类型,而这个Value是一个Func<ILifetime,object>委托,这个委托的具体实例是通过FactoryFromType(implementation)构建的,还记得这个方法吗?这个方法是通过传入ILifetime通过反射每次都构建一个新的对象,这个就是我们常说的瞬时对象,即每一次都new一个新的对象,这个是默认的实现。但是我们发现下面还有连个公共的方法,我们以AsSingleton方法为例,调用这个方法的时候还是调用RegisteredType构造函数中传入的Action委托,但是我们发现这个时候参数变成了lifetime => lifetime.GetServiceAsSingleton(_itemType, _factory)这个表达式,还记得lifetime的GetServiceAsSingleton方法代表的是什么吗?这个是从ObjectCache中一个线程安全的字典中获取或者增加对象,通过这个委托我们能够实现调用这个委托的时候每一次都获取的是同一个实例,这样就能够实现单例模式,到了这里整个过程是不是就豁然开朗了,到这里基本上整个过程的思路就缕清了,怎么样这个设计确实好,而且非常巧妙。

  1. 解析过程

这个过程相反,如果你理解了上面的整个注册的过程你会发现这个是非常简单的就是从 _registeredTypes这个Dictionary中通过对应的接口定义找到Func<ILifetime,object>定义然后调用这个委托就能够获取最终的对象,我们来看看这个具体的代码

 /// <summary>
        /// Returns the object registered for the given type, if registered
        /// </summary>
        /// <param name="type">Type as registered with the container</param>
        /// <returns>Instance of the registered type, if registered; otherwise <see langword="null"/></returns>
        public object GetService(Type type)
        {
            Func<ILifetime, object> registeredType;

            if (!_registeredTypes.TryGetValue(type, out registeredType))
            {
                return null;
            }

            return registeredType(_lifetime);
        }

这个部分最后传入的ILifetime接口是一个成员变量_lifetime,这个在Container中定义了一个ContainerLifetime,我们来看看具体的初始化过程。

 /// <summary>
        /// Creates a new instance of IoC Container
        /// </summary>
        public Container() => _lifetime = new ContainerLifetime(t => _registeredTypes[t]);

总结

这篇文章完整讲述了整个注册类型和解析类型的完整过程,为了更好的理解整个过程建议将源码下载下来对照单元测试来好好进行理解,通过这个例子能够对真个IOC容器的注册和解析有一个更清晰的认知,其它过程肯定是基于此原理进行更多功能的扩展,这篇文章非常建议认知收藏然后仔细精读因为里面的设计过程非常巧妙,很值得认真去学习体会。

C#通过MinIoc源码带你了解Ioc容器实现

上一篇:软件世界的基石:重要开源项目盘点


下一篇:wps会员账号免费分享