My.Ioc 代码示例——Lifetime 和 ILifetimeScope

很多 Ioc 框架在创建对象的过程中,都会采取某种方式来缓存/复用/释放已构建的对象。在 My.Ioc 中,这个目的是通过 Lifetime/ILifetimeScope 来实现的。其中,Lifetime 实现了缓存/复用对象的功能,ILifetimeScope 则实现了复用/释放对象的功能。

My.Ioc 默认提供了三种 Lifetime:ContainerLifetime、TransientLifetime 和 ScopeLifetime。这里简单解释一下它们的含义:ContainerLifetime 继承自 SingletonLifetime,它实际上是一种单例模式的实现。TransientLifetime 顾名思义,即每次请求都新建一个对象返回给调用者。ScopeLifetime 则表明在某个 scope 及其父 scope 中创建的对象将在该 scope 内复用。

上面这样解释也许大家不是很容易明白,下面我们结合示例代码来说明:

using System;
using System.Diagnostics;
using My.Ioc;
using My.Ioc.Exceptions;

namespace LifetimeAndLifetimeScope
{
    #region Test Types

    public class SingletonDisposableClass : IDisposable
    {
        public void Dispose()
        {
            Console.WriteLine("Disposing SingletonDisposableClass...");
        }
    }
    public class SingletonNonDisposableClass { }

    public class TransientNonDisposableClass { }
    public class TransientDisposableClass : IDisposable 
    { 
        public void Dispose()
        {
            Console.WriteLine("Disposing TransientDisposableClass...");
        }
    }

    public class ScopedDisposableClass : IDisposable
    {
        public void Dispose()
        {
            Console.WriteLine("Disposing ScopedDisposableClass...");
        }
    }
    public class ScopedNonDisposableClass
    {
    }

    #endregion

    class Program
    {
        static void Main(string[] args)
        {
            var container = new ObjectContainer(false);
            Register(container);
            
            ResolveTransient(container);
            ResolveScope(container);
            ResolveSingleton(container);
            
            // Dispose the container
            // The disposable singleton instances should be disposed here.
            container.Dispose();
            Console.ReadLine();
        }

        static void Register(IObjectContainer container)
        {
            container.Register<TransientDisposableClass>()
                .In(Lifetime.Transient()); // This line can be omitted
            container.Register<TransientNonDisposableClass>()
                .In(Lifetime.Transient()); // This line can be omitted

            container.Register<ScopedDisposableClass>()
                .In(Lifetime.Scope());
            container.Register<ScopedNonDisposableClass>()
                .In(Lifetime.Scope());
            
            container.Register<SingletonDisposableClass>()
                .In(Lifetime.Container()); //Singleton
            container.Register<SingletonNonDisposableClass>()
                .In(Lifetime.Container()); //Singleton
            
            container.CommitRegistrations();
        }

        static void ResolveTransient(IObjectContainer container)
        {
            var nonDisposable1 = container.Resolve<TransientNonDisposableClass>();
            var nonDisposable2 = container.Resolve<TransientNonDisposableClass>();
            Debug.Assert(nonDisposable1 != nonDisposable2, "nonDisposable1 == nonDisposable2");

            try
            {
                var disposable_Error = container.Resolve<TransientDisposableClass>();
            }
            catch (Exception ex)
            {
                Debug.Assert(ex is InvalidLifetimeScopeException);
            }

            using (var scope = container.BeginLifetimeScope())
            {
                var disposable1 = container.Resolve<TransientDisposableClass>();
                var disposable2 = container.Resolve<TransientDisposableClass>();
                Debug.Assert(disposable1 != disposable2, "disposable1 == disposable2");
            }
        }

        static void ResolveScope(IObjectContainer container)
        {
            try
            {
                var nondisposable_Error = container.Resolve<ScopedNonDisposableClass>();
            }
            catch (Exception ex)
            {
                Debug.Assert(ex is InvalidLifetimeScopeException);
            }

            string nested_scope_should_share_instance =
                "{0} should be the same to {1}, because the {0} is resolved in the outer scope, "
                + "which is shared with the scope where {1} is resolved, so they must be a same instance.";

            string same_scope_should_share_instance =
                "{0} should be the same to {1}, because they are resolved in the same scope.";
           
            using (var scope1 = container.BeginLifetimeScope())
            {
                var disposable1 = scope1.Resolve<ScopedDisposableClass>();
                var nonDisposable1 = scope1.Resolve<ScopedNonDisposableClass>();
                
                using (var scope2 = scope1.BeginLifetimeScope())
                {
                    var disposable2 = scope2.Resolve<ScopedDisposableClass>();
                    var disposable3 = scope2.Resolve<ScopedDisposableClass>();
                    Debug.Assert(disposable1 == disposable2,
                        string.Format(nested_scope_should_share_instance, "disposable1", "disposable2"));
                    Debug.Assert(disposable1 == disposable3,
                        string.Format(nested_scope_should_share_instance, "disposable1", "disposable3"));
                    Debug.Assert(disposable2 == disposable3,
                        string.Format(same_scope_should_share_instance, "disposable2", "disposable3"));
                    
                    var nonDisposable2 = scope1.Resolve<ScopedNonDisposableClass>();
                    var nonDisposable3 = scope1.Resolve<ScopedNonDisposableClass>();
                    Debug.Assert(nonDisposable1 == nonDisposable2,
                        string.Format(nested_scope_should_share_instance, "nonDisposable1", "nonDisposable2"));
                    Debug.Assert(nonDisposable1 == nonDisposable3,
                        string.Format(nested_scope_should_share_instance, "nonDisposable1", "nonDisposable3"));
                    Debug.Assert(nonDisposable2 == nonDisposable3,
                        string.Format(same_scope_should_share_instance, "nonDisposable2", "nonDisposable3"));
                }
            }
        }
        
        static void ResolveSingleton(IObjectContainer container)
        {
            var disposable1 = container.Resolve<SingletonDisposableClass>();
            var disposable2 = container.Resolve<SingletonDisposableClass>();
            Debug.Assert(disposable1 == disposable2, "disposable1 != disposable2");
            
            var nonDisposable1 = container.Resolve<SingletonDisposableClass>();
            var nonDisposable2 = container.Resolve<SingletonDisposableClass>();
            Debug.Assert(nonDisposable1 == nonDisposable2, "disposable1 != nonDisposable2");
        }
    }
}
View Code

在示例中,我们设计了这么三对类型:SingletonDisposableClass/SingletonNonDisposableClass、ScopedDisposableClass/ScopedNonDisposableClass 以及 TransientNonDisposableClass/TransientDisposableClass。用意很简单,分别注册到上面三种类型的 Lifetime 中。而每一类对象之所以有 Disposable 和 NonDisposable 两个类型,是因为我们要展示这三类 Lifetime 中对象清理的策略。

我们首先在 Register 方法中将 SingletonDisposableClass/SingletonNonDisposableClass 注册为 Container 生命周期,将 ScopedDisposableClass/ScopedNonDisposableClass 注册为 Scope 生命周期,并将 TransientNonDisposableClass/TransientDisposableClass 注册为 Transient 生命周期。

做好了准备工作之后,下面我们要让容器来为我们创建上述对象。首先,我们看 ResolveTransient 这个方法。这个方法旨在告诉我们:

  1. 如果对象被注册为 Transient 生命周期,那么当我们请求容器解析对象时,无论我们是否使用 ILifetimeScope,每次返回给我们的都是容器新创建的对象。也就是说,两次解析返回的结果不是同一个对象
  2. 如果对象实现了 IDisposable 接口,那么在解析该对象时,就必须先向容器请求一个 ILifetimeScope,然后在该 ILifetimeScope 中解析对象。否则,您将会收到一个 InvalidLifetimeScopeException 的异常。而且,在 ILifetimeScope 结束 (Dispose) 时,所请求的对象也会随之被清理 (Dispose)

接着,我们来看一下 ResolveScope 这个方法。这个方法是我们用来演示 ScopeLifetime 的运行方式的。它告诉我们:

  1. 当对象被注册为 Scope 生命周期时,无论其是否实现 IDisposable 接口,在解析时我们都必须先向容器请求一个 ILifetimeScope,然后在该 ILifetimeScope 中解析对象。否则,您将会收到一个 InvalidLifetimeScopeException 的异常。
  2. 只有实现了 IDisposable 接口的对象才会在解析后,随着自身所处的 ILifetimeScope 的结束而被清理 (Dispose)。
  3. ILifetimeScope 是可以嵌套的。例如,在示例代码中,scope2 是嵌套在 scope1 中的。在这种情况下,我们将 scope1 称为外层(父) scope,将 scope2 称为内层(子) scope。
  4. 内层 scope 可以共享外层 scope 的对象,所以我们才会看到示例代码中有 [disposable1 == disposable2]、[nonDisposable1 == nonDisposable2] 等那么多相等性断言。

最后我们来看 ResolveSingleton 这个方法。我们使用这个方法来观察 ContainerLifetime 的运行方式。这个方法的运行结果告诉我们:

  1. 如果对象被注册为 Container 生命周期,那么当我们请求容器解析对象时,无论该对象是否实现了 IDisposable 接口,都不需要使用事先向容器请求 ILifetimeScope。但当然,要使用 ILifetimeScope 来解析它们也是可以的,最后结果应该都是返回相同实例。
  2. 如果所解析的对象实现了 IDisposable 接口,那么它要一直等到容器 (IObjectContainer) 本身的生命周期结束之后才被清理。

 

本文源码可在此处下载,压缩包中包含了 My.Ioc 框架的源码和本示例以及其他一些示例的源码。

posted @ 2014-09-12 19:20  Johnny.Liu  阅读(961)  评论(1编辑  收藏  举报