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"); } } }
在示例中,我们设计了这么三对类型:SingletonDisposableClass/SingletonNonDisposableClass、ScopedDisposableClass/ScopedNonDisposableClass 以及 TransientNonDisposableClass/TransientDisposableClass。用意很简单,分别注册到上面三种类型的 Lifetime 中。而每一类对象之所以有 Disposable 和 NonDisposable 两个类型,是因为我们要展示这三类 Lifetime 中对象清理的策略。
我们首先在 Register 方法中将 SingletonDisposableClass/SingletonNonDisposableClass 注册为 Container 生命周期,将 ScopedDisposableClass/ScopedNonDisposableClass 注册为 Scope 生命周期,并将 TransientNonDisposableClass/TransientDisposableClass 注册为 Transient 生命周期。
做好了准备工作之后,下面我们要让容器来为我们创建上述对象。首先,我们看 ResolveTransient 这个方法。这个方法旨在告诉我们:
- 如果对象被注册为 Transient 生命周期,那么当我们请求容器解析对象时,无论我们是否使用 ILifetimeScope,每次返回给我们的都是容器新创建的对象。也就是说,两次解析返回的结果不是同一个对象。
- 如果对象实现了 IDisposable 接口,那么在解析该对象时,就必须先向容器请求一个 ILifetimeScope,然后在该 ILifetimeScope 中解析对象。否则,您将会收到一个 InvalidLifetimeScopeException 的异常。而且,在 ILifetimeScope 结束 (Dispose) 时,所请求的对象也会随之被清理 (Dispose)。
接着,我们来看一下 ResolveScope 这个方法。这个方法是我们用来演示 ScopeLifetime 的运行方式的。它告诉我们:
- 当对象被注册为 Scope 生命周期时,无论其是否实现 IDisposable 接口,在解析时我们都必须先向容器请求一个 ILifetimeScope,然后在该 ILifetimeScope 中解析对象。否则,您将会收到一个 InvalidLifetimeScopeException 的异常。
- 只有实现了 IDisposable 接口的对象才会在解析后,随着自身所处的 ILifetimeScope 的结束而被清理 (Dispose)。
- ILifetimeScope 是可以嵌套的。例如,在示例代码中,scope2 是嵌套在 scope1 中的。在这种情况下,我们将 scope1 称为外层(父) scope,将 scope2 称为内层(子) scope。
- 内层 scope 可以共享外层 scope 的对象,所以我们才会看到示例代码中有 [disposable1 == disposable2]、[nonDisposable1 == nonDisposable2] 等那么多相等性断言。
最后我们来看 ResolveSingleton 这个方法。我们使用这个方法来观察 ContainerLifetime 的运行方式。这个方法的运行结果告诉我们:
- 如果对象被注册为 Container 生命周期,那么当我们请求容器解析对象时,无论该对象是否实现了 IDisposable 接口,都不需要使用事先向容器请求 ILifetimeScope。但当然,要使用 ILifetimeScope 来解析它们也是可以的,最后结果应该都是返回相同实例。
- 如果所解析的对象实现了 IDisposable 接口,那么它要一直等到容器 (IObjectContainer) 本身的生命周期结束之后才被清理。
本文源码可在此处下载,压缩包中包含了 My.Ioc 框架的源码和本示例以及其他一些示例的源码。