何为 Resue

Reuse(复用)或者 lifeStyle(存活方式)指的是容器只需创建一次服务实例,即可在后续每次解析或注入时返回相同的实例(重复使用同一个服务实例)。服务实例对象能在多个“消费者”之间共享。 在软件开发中,最为知名复用方式便是“单例模式”(Singletion)。

DryIoc 提供以下几类复用类型:

  • Transient
  • Singleton
  • Scoped
  • ScopedOrSingleton

基于 Scoped 衍生出以下类型:

  • ScopedTo(scopeName(s))
  • ScopedTo()
  • ScopedTo(serviceKey)
  • InThread
  • InWebRequest

服务设置选项(Setup):

  • Setup.UseParentReuse
  • DecoratorSetup.UseDecorateeReuse

通过继承 IReuse 接口实现自定义复用规则。

Scope 实现了 IDisposable 接口,在 Scope 对象释放的同时,也会释放该作用域之中的所有服务对象。可通过(setup)对其进行各项设置。 服务的释放顺序总之以注册顺序的逆序进行。

Resue.Transient

每次需要解析或注入对象时都会创建一个新实例。在注册时未提供 resue 参数 或 未曾更改 Container.Rules.DefaultReuse 的前提下,容器的默认的复用方式便是 Transient。

以下代码本质是一致的:

  1. container.Register<IFoo, Foo>(Reuse.Transient);
  2. container.Register<IFoo, Foo>(); // 默认使用 Reuse.Transient

释放 Transient

疑问 :当在 Transient 方式下注册的服务实现了 IDisposable 接口,那么应该由谁负责释放服务实例?

DryIco 默认情况下不支持注册实现了 IDispoabled 接口的类型(即:register 一个实现 IDisposable 的类型时会引发错误),可通过以下两种方法解决此类问题:

  • 使用 allowDisposableTransient: true 来设置单个注册信息(通过 Setup.With 方法设置改参数),
  • 使用 Rules .WithoutThrowOnRegisteringDisposableTransient() 方法设置整个容器。

    示例 :使用 allowDisposableTransient: true (或Rules .WithoutThrowOnRegisteringDisposableTransient 方法)注册可释放的临时服务

    1. class Disposable_transient_as_resolved_service
    2. {
    3. [Test]
    4. public void Example()
    5. {
    6. var container = new Container();
    7. // ...或者使用以下代码应用于整个容器
    8. //var container = new Container(rules => rules.WithoutThrowOnRegisteringDisposableTransient());
    9. container.Register<X>(setup: Setup.With(allowDisposableTransient: true));
    10. // 通过 x 引用可以随时释放该实例
    11. var x = container.Resolve<X>();
    12. x.Dispose();
    13. }
    14. class X : IDisposable { public void Dispose() { } }
    15. }

    疑问 : 通过注入的形式解析 IDispoabled 对象时(例如:IDispabled 对象作为其他类型的属性,通过构造函数注入),若此时无法直接获取 IDisposable 对象引用时,该如何释放?

    示例 :构造函数注入的对象无法通过引用手动释放

    1. class Disposable_transient_as_injected_dependency
    2. {
    3. [Test]
    4. public void Example()
    5. {
    6. // global option
    7. var container = new Container(rules => rules.WithoutThrowOnRegisteringDisposableTransient());
    8. container.Register<XUser>();
    9. container.Register<X>();
    10. // 或者使用以下代码只应用于单个注册
    11. //container.Register<X>(setup: Setup.With(allowDisposableTransient: true));
    12. var user = container.Resolve<XUser>();
    13. // 此时 X类型的引用无法获取,该如何释放?
    14. //x.Dispose();
    15. }
    16. class X : IDisposable { public void Dispose() { } }
    17. class XUser { public XUser(X x) { } }
    18. }

    DryIoc 提供了一种方法用于在当前已开启的作用域中跟踪可释放的临时对象。

    示例 :OpenScope 方法基本使用

    1. class Tracking_disposable_transient
    2. {
    3. [Test]
    4. public void Example()
    5. {
    6. // 这里通过 Rules 启用瞬态对象跟踪(WithTrackingDisposableTransients),默认是不启动的。这里是针对容器进行全局设置。
    7. var container = new Container(rules => rules.WithTrackingDisposableTransients());
    8. // 或者,在这里通过 Setup 启用瞬态对象跟踪(trackDisposableTransient),默认时启动的。这里是针对 X 类型的注册进行全局设置。
    9. //container.Register<X>(setup: Setup.With(trackDisposableTransient: true));
    10. container.Register<X>();
    11. container.Register<XUser>();
    12. XUser user;
    13. // 使用 OpenScope 创建并开打一个新的作用域
    14. using (var scope = container.OpenScope())
    15. {
    16. user = scope.Resolve<XUser>();
    17. } // scope 对象的生命周期结束,using 语句自动调用 scope.Dispose() 方法
    18. // Assert通过,随着 scope 的释放,在该作用域解析的对象也同样释放
    19. Assert.IsTrue(user.X.IsDisposed);
    20. }
    21. class X : IDisposable
    22. {
    23. public bool IsDisposed { get; private set; }
    24. public void Dispose() => IsDisposed = true;
    25. }
    26. class XUser
    27. {
    28. public readonly X X;
    29. public XUser(X x) { X = x; }
    30. }
    31. }

    疑问 :如何阻止 Scope 释放其解析的对象?

    示例 :使用 preventDisposal 设置选项阻止特定服务的跟踪

    1. class Tracking_disposable_transient
    2. {
    3. [Test]
    4. public void Example()
    5. {
    6. // 这里通过 Rules 启用瞬态对象跟踪(WithTrackingDisposableTransients),默认是不启动的。
    7. var container = new Container(rules => rules.WithTrackingDisposableTransients());
    8. // 使用 Setup.With(preventDisposal: true) 阻止容器释放该注册类型
    9. container.Register<X>(setup: Setup.With(preventDisposal: true));
    10. container.Register<XUser>();
    11. XUser user;
    12. using (var scope = container.OpenScope())
    13. {
    14. user = scope.Resolve<XUser>();
    15. }
    16. // Assert通过,X 类型的参数并不会这 scope 的释放而释放
    17. Assert.IsFalse(user.X.IsDisposed);
    18. }
    19. class X : IDisposable
    20. {
    21. public bool IsDisposed { get; private set; }
    22. public void Dispose() => IsDisposed = true;
    23. }
    24. class XUser
    25. {
    26. public readonly X X;
    27. public XUser(X x) { X = x; }
    28. }
    29. }

    示例 :使用 Func 设置选项阻止特定服务的跟踪

    1. class Prevent_disposable_tracking_with_Func
    2. {
    3. [Test]
    4. public void Example()
    5. {
    6. var container = new Container(rules => rules.WithTrackingDisposableTransients());
    7. container.Register<XFactory>();
    8. container.Register<X>();
    9. var xf = container.Resolve<XFactory>();
    10. var x = xf.GetX();
    11. // 手动释放容器
    12. container.Dispose();
    13. // Assert通过,因为 xf 对象的 X 属性随着容器释放而释放
    14. Assert.IsTrue(xf.X.IsDisposed);
    15. // Assert通过,因为由 Func<T> 解析的对象不会随着容器释放而释放
    16. Assert.IsFalse(x.IsDisposed);
    17. }
    18. class XFactory
    19. {
    20. public readonly Func<X> GetX;
    21. public readonly X X;
    22. public XFactory(Func<X> getX, X x)
    23. {
    24. GetX = getX;
    25. X = x;
    26. }
    27. }
    28. class X : IDisposable
    29. {
    30. public bool IsDisposed { get; private set; }
    31. public void Dispose() => IsDisposed = true;
    32. }
    33. }

    使用不同的 Reuse 代替 Transient

    示例 :使用 rule 更改容器的复用方式

    1. class Default_reuse_per_container
    2. {
    3. [Test]
    4. public void Example()
    5. {
    6. // 这里通过 Rules 更改默认值
    7. var container = new Container(rules => rules.WithDefaultReuse(Reuse.Scoped));
    8. container.Register<Abc>();
    9. using (var scope = container.OpenScope())
    10. {
    11. var abc = scope.Resolve<Abc>();
    12. Assert.AreSame(abc, scope.Resolve<Abc>());
    13. }
    14. }
    15. class Abc { }
    16. }

    Reuse.Singleton

    一个容器存在一个服务实例(唯一),该服务实例在首次解析或首次注入时被创建,并且一直存活直到容器释放。如果该服务实例继承自 IDisposable ,那会与容器一起释放。

示例 :单例模式的基本示范

  1. class Singleton_reuse
  2. {
  3. [Test]
  4. public void Example()
  5. {
  6. var container = new Container();
  7. // 这里将类型 A 注册为单例模式
  8. container.Register<A>(Reuse.Singleton);
  9. var a = container.Resolve<A>();
  10. Assert.AreSame(a, container.Resolve<A>());
  11. }
  12. class A { }
  13. }

Reuse.Scoped

什么是作用域(Scoped)

物理作用域(Physicall scope)用于存储那些非瞬时对象(除了可释放临时对象)。复用对象一旦被创建,便存储在作用域的内部集合中,一直存活直到作用域被释放。另外,作用域(Scope)能够确保在多线程场景中只创建一次服务实例。

什么是当前作用域(Current Scope)

当执行 var scopedContainer = container.OpenScope(); 时,即可创建并打开“当前作用域”,或者通过 var nestedScopedContainer=scopedContainer.OpenScope(); 在当前作用域中创建“嵌套作用域”。 上述代码执行返回的结果类型是 IResolverContext。实际上,它是一个全新的容器,它共享着原容器所有的注册信息以及解析缓存,但指向的是一个新的作用域。

示例 :在作用域中解析可释放对象

  1. class Scoped_reuse_register_and_resolve
  2. {
  3. [Test]
  4. public void Example()
  5. {
  6. var container = new Container();
  7. container.Register<Car>(Reuse.Scoped);
  8. // Assert通过,抛出异常信息
  9. Assert.Throws<ContainerException>(() => container.Resolve<Car>());
  10. Car car;
  11. using (var scopedContainer = container.OpenScope())
  12. {
  13. car = scopedContainer.Resolve<Car>();
  14. car.DriveToMexico();
  15. } // scopedContainer 生周周期结束,using 调用它的 Dispose() 方法
  16. // Assert通过,car 对象随着 scopedContainer 的释放而释放
  17. Assert.IsTrue(car.IsDisposed);
  18. }
  19. class Car : IDisposable
  20. {
  21. public void DriveToMexico() { }
  22. public void Dispose() => IsDisposed = true;
  23. public bool IsDisposed { get; private set; }
  24. }
  25. }

如果在 Resolve 对象用的 Func 或 Lazy 进行包装,那么将不会引发异常。但是访问“值”时会引发异常。

示例 :Lazy 在新作用域中访问 Value 引发异常

  1. class Scoped_reuse_resolve_wrapped_in_Lazy_outside_of_scope
  2. {
  3. [Test]
  4. public void Example()
  5. {
  6. var container = new Container();
  7. container.Register<Car>(Reuse.Scoped);
  8. // 这里解析的不是 Car 对象,而是 Lazy<Car>,由于惰性加载的性质,不会引发异常
  9. var carFactory = container.Resolve<Lazy<Car>>();
  10. Car car = null;
  11. using (var scopedContainer = container.OpenScope())
  12. {
  13. Assert.Throws<ContainerException>(() => car = carFactory.Value);
  14. }
  15. }
  16. class Car
  17. {
  18. public void DriveToMexico() { }
  19. }
  20. }

当 container.Resolve>时,默认情况下会引发异常(因为 container 没有打开新的作用域),不过由于 Lazy 和 Func 是惰性加载机制,因此阻止了容器抛出异常。当 container.OpenScope()时,容器打开了新的作用域,之前的 car Factory 并不获知新的作用域,因此在访问 carFactory.Value 时会引发异常。

作用域上下文(ScopeContext)

ScopeContext (实现 IScopeContext 接口)针对当前作用域和嵌套作用域的一种共享存储和跟踪的机制。默认情况下,容器没有任何作用域上下文,而是在自身中直接存储作用域。当提供了作用域,容器与作用域容器之间会共享作用域上下文。通过共享作用域上下,使得在作用域外通过惰性解析随后获取 Value 方式成为了一种可行的做法。

示例 :使用作用域上下文,解决Lazy 在新作用域中访问 Value 引发异常

  1. class Scoped_reuse_resolve_Lazy_with_scope_context
  2. {
  3. [Test]
  4. public void Example()
  5. {
  6. // 这里通过 scopeContext 参数传入一个作用域上下文对象 AsyncExecutionFlowScopeContext
  7. var container = new Container(scopeContext: new AsyncExecutionFlowScopeContext());
  8. container.Register<Car>(Reuse.Scoped);
  9. var carFactory = container.Resolve<Lazy<Car>>();
  10. using (var scopedContainer = container.OpenScope())
  11. {
  12. var car = carFactory.Value;
  13. car.DriveToMexico();
  14. }
  15. }
  16. class Car
  17. {
  18. public void DriveToMexico() { }
  19. }
  20. }

DryIoc 默认支持的作用域上下文:(可通过实现 IScopeContext 接口实现自定义扩展)

  • AsyncExecutionFlowScopeContext:适用于 .NET 4.5+ 和 .NET Standard 1.3+。
  • ThreadScopeContext:线程本地上下文,在当前线程中共享打开的作用域。
  • HttpContextScopeContext:适用于 DryIoc.Web 扩展。打开的作用域存储在 HttpContext 中。

备注:如果对作用域上下文类型不熟悉,直接选择 AsyncExecutionFlowScopeContext 作为默认设置即可。

嵌套作用域

示例 :链式作用域基本使用(无作用域上下文)

  1. class Nested_scopes_without_scope_context
  2. {
  3. [Test]
  4. public void Example()
  5. {
  6. var container = new Container();
  7. container.Register<A>(Reuse.Scoped);
  8. // Three nested scopes
  9. var s1 = container.OpenScope();
  10. var s2 = s1.OpenScope();
  11. var s3 = s2.OpenScope();
  12. // 在 Resue.Scoped 模式下,相同层级之间作用域解析的对象是同一对象
  13. Assert.AreSame(s1.Resolve<A>(), s1.Resolve<A>());
  14. // 在 Resue.Scoped 模式下,不同层级之间作用域解析的对象不是同一对象
  15. Assert.AreNotSame(s1.Resolve<A>(), s2.Resolve<A>());
  16. Assert.AreNotSame(s2.Resolve<A>(), s3.Resolve<A>());
  17. }
  18. class A { }
  19. }

备注:在 DryIoc中,单例作用域并不是作用域链中的一部分。

在没有作用域上下文的情况下,所有的嵌套作用域均是独立存在,因此可以随时在任何嵌套的作用域容器中进行解析出不同的服务实例。

示例 :链式作用域基本使用(提供作用域上下文)

  1. class Nested_scopes_with_scope_context
  2. {
  3. [Test]
  4. public void Example()
  5. {
  6. // 这里通过 scopeContext 传递一个 AsyncExecutionFlowScopeContext 对象
  7. var container = new Container(scopeContext: new AsyncExecutionFlowScopeContext());
  8. container.Register<A>(Reuse.Scoped);
  9. // Three nested scopes
  10. var s1 = container.OpenScope();
  11. var s2 = s1.OpenScope();
  12. var s3 = s2.OpenScope();
  13. Assert.AreSame(s1.Resolve<A>(), s1.Resolve<A>());
  14. // 所有 Resolve 出来的对象均存放于作用域 s3
  15. Assert.AreSame(s1.Resolve<A>(), s2.Resolve<A>());
  16. Assert.AreSame(s2.Resolve<A>(), s3.Resolve<A>());
  17. // 虽然通过 s1 作用域来解析,但实际上还是存储在作用域 s3 中
  18. var a = s1.Resolve<A>();
  19. // 释放作用域 s3 ,服务实例 a 也随之释放,同时作用域 s2 作为当前作用域
  20. s3.Dispose();
  21. Assert.IsTrue(a.IsDisposed);
  22. // 因此,作用域 s2 解析的新实例 A 与之前 s3 解析的实例 a 并不相同
  23. Assert.AreNotSame(a, s2.Resolve<A>());
  24. Assert.AreSame(s2.Resolve<A>(), s1.Resolve<A>());
  25. }
  26. class A : IDisposable
  27. {
  28. public void Dispose() => IsDisposed = true;
  29. public bool IsDisposed { get; private set; }
  30. }
  31. }

作用域上下文与容器时依靠“current”作用域属性关联在一起的,它指向的是 “最后/最底层”的 作用域。“current”作用域会在每打开一次新的作用域时被覆写,使其成为实际执行解析操作的作用域。当“current”作用域被释放,那么它的父作用域将会成为新的“current”作用域。

Reuse.ScopedTo(name)

可利用“name”区分作用域,在链式作用域中通过 Reuse.ScopedTo(object name) 来指定使用某作用域用于执行解析操作。

  1. class Named_open_scopes_and_scoped_to_name
  2. {
  3. [Test]
  4. public void Example()
  5. {
  6. var container = new Container();
  7. // 这里使用 Resue.ScopedTo 为 Car 类的注册指定了特定名称的作用域
  8. container.Register<Car>(Reuse.ScopedTo("top"));
  9. using (var s1 = container.OpenScope("top"))
  10. {
  11. var car1 = s1.Resolve<Car>();
  12. using (var s2 = s1.OpenScope())
  13. {
  14. var car2 = s2.Resolve<Car>();
  15. // 尽管 car2 是通过嵌套的作用域 s2 解析,但 car2 和 car1 仍是引用同一对象。
  16. // 因为它是通过指定的作用域 s1 进行解析的
  17. Assert.AreSame(car2, car1);
  18. }
  19. }
  20. }
  21. class Car { }
  22. }

当解析或注入服务时使用 ScopeTo(name) ,那么 DryIoc 将会从当前作用域沿着链式作用域开始查找,直到找到名字匹配作用域或者顶部作用域。
任何支持 Equals(object other) 和 GetHashCode() 的类对象均可作为区分标识的“name”。
Reuse.ScopedTo(42) 或 Reuse.ScopedTo(Flags.Red),其中 42 和 Flags.Red 都是有效的“name”。

Reuse.InWebRequest and Reuse.InThread

Reuse.InWebRequest 和 Reuse.InThread 表示:

  • Reuse.InWebRequest == Reuse.ScopedTo(specificName).
  • Reuse.InThread == Reuse.Scoped

    暂时未搞懂

    Reuse.ScopedTo 特定类型

    示例 :openResolutionScope 开启“解析作用域”

    1. class Scoped_to_service_reuse
    2. {
    3. [Test] public void Example()
    4. {
    5. var container = new Container();
    6. // 这里使用 Setup 设置 openResolutionScope:true,用于指定为 Foo 类开启 “解析作用域”
    7. container.Register<Foo>(setup: Setup.With(openResolutionScope: true));
    8. container.Register<Dependency>();
    9. // 这里使用 Reuse.ScopedTo ,让 SubDependency 类在指定的作用域解析时复用
    10. // 即:在解析Foo类的时候,需要注入 SubDependency 类,后者只会创建一次,然后复用
    11. container.Register<SubDependency>(Reuse.ScopedTo<Foo>());
    12. var foo = container.Resolve<Foo>();
    13. // Assert通过
    14. Assert.AreSame(foo.Sub, foo.Dep.Sub);
    15. }
    16. class Foo
    17. {
    18. public SubDependency Sub { get; }
    19. public Dependency Dep { get; }
    20. public Foo(SubDependency sub, Dependency dep)
    21. {
    22. Sub = sub;
    23. Dep = dep;
    24. }
    25. }
    26. class Dependency
    27. {
    28. public SubDependency Sub { get; }
    29. public Dependency(SubDependency sub)
    30. {
    31. Sub = sub;
    32. }
    33. }
    34. class SubDependency { }
    35. }

    流程分析:

  1. Foo 注册时设置了“解析作用域”,表明在解析 Foo 对象时会开启一个新的作用域,以 Foo 类为区分标识;
  2. SubDependency 注册时指定使用 Foo 类的解析作用域执行解析操作,解析出的对象在该作用域中复用。
  3. 在解析 Foo 对象时,其构造函数需要注入 SubDependecy 与 Dependency ,此时开启 Foo 类作用域。
  4. SubDependecy 类对象被创建并保存进 Foo 作用域当中,然后注入到 Foo 的构造函数。
  5. Dependecy 类解析时,其构造函数需要注入 SubDependecy 类对象,此时从作用域中提取复用的 SubDependecy 对象并注入到 Dependecy 的构造函数当中。
  6. Dependecy 类对象被创建并保存进 Foo 作用域当中,然后注入到 Foo 的构造函数。
  7. Foo 类对象解析操作完成。

代码 openResolutionScope:true 已经阐释了 ScopedTo() 是如何工作的。它与 ScopedTo(object someName) 没有本质差异,只不过它的 “someName” 是一种由 typeof(Foo) 和 可选的 service key 构成的特殊类型 ResolutionScopeName。

  1. // DryIoc 会在解析 Foo 对象时使用新的作用域
  2. var foo = container.OpenScope(new ResolutionScopeName(typeof(Foo))).Resolve<Foo>();

示例 :“解析作用域”随着上层作用域的释放而释放

  1. class Scoped_to_service_reuse_with_dispose
  2. {
  3. [Test] public void Example()
  4. {
  5. var container = new Container();
  6. container.Register<Foo>(setup: Setup.With(openResolutionScope: true));
  7. container.Register<Dependency>(Reuse.ScopedTo<Foo>());
  8. var foo = container.Resolve<Foo>();
  9. container.Dispose();
  10. // Assert通过,Foo 解析作用域 随着上层作用域(容器)释放而释放
  11. Assert.IsTrue(foo.Dep.IsDisposed);
  12. }
  13. class Foo
  14. {
  15. public Dependency Dep { get; }
  16. public Foo(Dependency dep) { Dep = dep; }
  17. }
  18. class Dependency : IDisposable
  19. {
  20. public bool IsDisposed { get; private set; }
  21. public void Dispose() => IsDisposed = true;
  22. }
  23. }

拥有解析作用域的释放权

通过注入 IResolverContext 对象(由容器自动注入)来获取当前作用域的引用,随后借助它便可手动释放它指向的作用域。

示例 :手动释放“解析作用域”

  1. class Own_the_resolution_scope_disposal
  2. {
  3. [Test]
  4. public void Example()
  5. {
  6. // 这里通过 Rules 启用瞬态对象跟踪(WithTrackingDisposableTransients),默认是不启动的。
  7. var container = new Container(rules => rules
  8. .WithTrackingDisposableTransients()
  9. );
  10. container.Register<Foo>(setup: Setup.With(openResolutionScope: true));
  11. container.Register<Dependency>(Reuse.ScopedTo<Foo>());
  12. var foo = container.Resolve<Foo>();
  13. // 释放 Foo 会释放它的作用域以及在作用域中的解析对象
  14. foo.Dispose();
  15. Assert.IsTrue(foo.Dep.IsDisposed);
  16. }
  17. class Foo : IDisposable
  18. {
  19. public Dependency Dep { get; }
  20. private readonly IResolverContext _scope;
  21. public Foo(Dependency dep, IResolverContext scope)
  22. {
  23. Dep = dep;
  24. _scope = scope;
  25. }
  26. public void Dispose() => _scope.Dispose();
  27. }
  28. class Dependency : IDisposable
  29. {
  30. public bool IsDisposed { get; private set; }
  31. public void Dispose() => IsDisposed = true;
  32. }
  33. }

关于 Resue.ScopedTo(object serviceKey = null):

  • 可以通过提供一个 serviceKey 来匹配注册时候的key。
  • 除了匹配精准的 TService 外,还可以匹配它的基类以及接口。

    Setup.UseParentReuse

    Setup.UserParentReuse 配置项允许依赖使用它的父元素或祖先元素的复用方式。若父元素或祖先元素的复用方式为 Transient 模式,那么将会跳过这些元素并向上查找首个非 Transient 模式的祖先元素。如果所有祖先都是 Transient 模式,或者依赖关系被封装在 Func 中,那么依赖关系最终将是 Transient 模式。

示例 :Setup.UseParentReuse 基本示例

  1. class Use_parent_reuse
  2. {
  3. [Test]
  4. public void Example()
  5. {
  6. var container = new Container();
  7. container.Register<Mercedes>(Reuse.Singleton);
  8. container.Register<Dodge>();
  9. container.Register<Wheels>(setup: Setup.With(useParentReuse: true));
  10. // m1 与 m2 的 wheels 是同一个对象
  11. var m1 = container.Resolve<Mercedes>();
  12. var m2 = container.Resolve<Mercedes>();
  13. Assert.AreSame(m1.Wheels, m2.Wheels);
  14. // m1 与 m2 的 wheels 是同一个对象
  15. var d1 = container.Resolve<Dodge>();
  16. var d2 = container.Resolve<Dodge>();
  17. Assert.AreNotSame(d1.Wheels, d2.Wheels);
  18. }
  19. class Mercedes
  20. {
  21. public Wheels Wheels { get; }
  22. public Mercedes(Wheels wheels)
  23. {
  24. Wheels = wheels;
  25. }
  26. }
  27. class Dodge
  28. {
  29. public Wheels Wheels { get; }
  30. public Dodge(Wheels wheels)
  31. {
  32. Wheels = wheels;
  33. }
  34. }
  35. private class Wheels { }
  36. }

备注:如果同时设置了 resue 和 useParentReuse ,那么 resue 具有更高的优先级,而 setup 将会被忽略。

Reuse Lifespan 诊断

在DryIoc中,IReuse 的实现类定义了 Lifespan属性。这是用于指示不同复用模式对应的生命周期的相对值(整型数值),通过简单的数值比较便可以查找错误的匹配。

  • Singletion:1000
  • Scoped(To):100
  • Transient:0

示例 :Leftspan 错误匹配示例

  1. class Reuse_lifespan_mismatch_detection
  2. {
  3. [Test]
  4. public void Example()
  5. {
  6. var container = new Container();
  7. container.Register<Mercedes>(Reuse.Singleton);
  8. container.Register<Wheels>(Reuse.Scoped);
  9. using (var scope = container.OpenScope())
  10. {
  11. // Assert通过,由于 Scoped 的 leftspan 小于 Singleton 的 leftspan,因此引发异常
  12. Assert.Throws<ContainerException>(() => scope.Resolve<Mercedes>());
  13. }
  14. }
  15. class Mercedes { public Mercedes(Wheels wheels) { } }
  16. class Wheels { }
  17. }

异常信息 :
DryIoc.ContainerException:“code: Error.DependencyHasShorterReuseLifespan;
message: Dependency ConsoleApp1.Wheels as parameter “wheels” (IsSingletonOrDependencyOfSingleton) with reuse Scoped {Lifespan=100} has a shorter lifespan than its parent’s resolution root Singleton ConsoleApp1.Mercedes FactoryId=144 (IsSingletonOrDependencyOfSingleton, IsResolutionCall from container with scope {Name=null}
If you know what you’re doing you may disable this error with the rule new Container(rules => rules.WithoutThrowIfDependencyHasShorterReuseLifespan()).”

示例 :关闭 Leftspan 错误匹配异常(方法1)

  1. class Reuse_lifespan_mismatch_error_suppress
  2. {
  3. [Test]
  4. public void Example()
  5. {
  6. // 这里通过 rules.WithoutThrowIfDependencyHasShorterReuseLifespan() 关闭 Leftspan 匹配错误异常
  7. var container = new Container(rules => rules.WithoutThrowIfDependencyHasShorterReuseLifespan());
  8. container.Register<Mercedes>(Reuse.Singleton);
  9. container.Register<Wheels>(Reuse.Scoped);
  10. Mercedes car;
  11. using (var scope = container.OpenScope())
  12. {
  13. car = scope.Resolve<Mercedes>();
  14. }
  15. // car对象(singleton)依旧存在且可访问,但它的 Wheels 属性已经被释放了
  16. Assert.IsTrue(car.Wheels.IsDisposed);
  17. }
  18. class Mercedes
  19. {
  20. public Mercedes(Wheels wheels) { Wheels = wheels; }
  21. public Wheels Wheels { get; }
  22. }
  23. class Wheels : IDisposable
  24. {
  25. public void Dispose() => IsDisposed = true;
  26. public bool IsDisposed { get; private set; }
  27. }
  28. }

另一种关闭错误匹配异常的方法就是使用 Func 或 Lazy 方法进行包装。

示例 :关闭 Leftspan 错误匹配异常(方法2)

  1. class Avoiding_reuse_lifespan_mismatch_for_Func_or_Lazy_dependency
  2. {
  3. [Test]
  4. public void Example()
  5. {
  6. var container = new Container();
  7. container.Register<Mercedes>(Reuse.Singleton);
  8. container.Register<Wheels>(Reuse.Scoped);
  9. using (var scope = container.OpenScope())
  10. {
  11. // Func<T> 的缘故, Resolve 不会引发错误
  12. var car = scope.Resolve<Mercedes>();
  13. var wheels = car.GetWheels();
  14. }
  15. }
  16. class Mercedes
  17. {
  18. // 构造函数需要注入 Func<Wheels> 对象
  19. public Mercedes(Func<Wheels> getWheels) { GetWheels = getWheels; }
  20. public readonly Func<Wheels> GetWheels;
  21. }
  22. class Wheels { }
  23. }

弱引用复用对象

  1. container.Register<Service>(Reuse.Singleton, setup: Setup.With(weaklyReferenced: true));

阻止复用对象的释放

  1. container.Register<Service>(Reuse.Singleton, setup: Setup.With(preventDisposal: true));