【DryIOC】作用域服务(Reuse.Scoped Service)

1. 什么是作用域?

DryIOC使用Scope实现工作单元模式。
从本质上说,作用域是用于存储已解析或已注入的除实现IDisposable接口的非瞬态已注册服务。一旦服务被创建,一个可重用的对象被存储在作用域的内部集合中,该对象一直保持存活状态,直到作用域被释放。此外,作用域能够保证在多线程场景下服务实例仅被创建一次

2. 什么是当前作用域?

当前作用通过var scopedContainer = container.OpenScope()var nestedScopedContainer = scopedContainer.OpenScope()进行创建。调用生成结果的类型是IResolverContext,实际上就是一个新的容器。新容器共享父容器的所有已注册的服务与缓存协议,但是包含一个打开的作用域。一个从新打开的作用域中解析出的标注为Reuse.Scope的服务,将被存储于这个打开的作用域。当这个作用域释放时,其存储的服务实例也将被释放。

void Main()
{
	var container = new Container();
	// 注册时标注为作用域服务,则在非作用域内解析或注入服务实例会产生异常,因为容器不包含任何作用域
	container.Register<A>(Reuse.Scoped);
	container.Register<B>(Reuse.Scoped);
	
	// 以下语句会会报错
	// container.Resolve<A>();
	
	var scopedContainer = container.OpenScope();
	scopedContainer.Resolve<A>();
	scopedContainer.Resolve<B>();

	var nestedScopedContainer = scopedContainer.OpenScope();
	nestedScopedContainer.Resolve<A>();
	nestedScopedContainer.Resolve<B>();
}

class A { }

class B { }

2.1 容器的内容

容器默认情况下不包含任何作用域,在创建作用域之后也不包含任何作用域。

void Main()
{
	Container container = new Container();
	container.Dump("Before Creating Scope");
	IResolverContext scopedContainer = container.OpenScope(name: "scopedContainer");
	container.Dump("After Creating Scope");
}

在这里插入图片描述

2.2 一个容器创建的多个作用域是否为相同的作用域?

void Main()
{
	Container container = new Container();
	IResolverContext scopedContainer1 = container.OpenScope(name: "scopedContainer1");
	IResolverContext scopedContainer2 = container.OpenScope(name: "scopedContainer2");
	Console.WriteLine("一个容器创建的多个作用域是否为相同的作用域: " + (scopedContainer1 == scopedContainer2));
}

通过一个容器创建的多个作用域是否为相同的作用域: False

2.3 一个作用域创建的多个子作用域是否为相同作用域?

void Main()
{
	Container container = new Container();
	IResolverContext scopedContainer = container.OpenScope(name: "scopedContainer");
	IResolverContext scopedContainer1 = scopedContainer.OpenScope(name: "scopedContainer1");
	IResolverContext scopedContainer2 = scopedContainer.OpenScope(name: "scopedContainer2");
	Console.WriteLine("一个容器创建的多个作用域是否为相同的作用域: " + (scopedContainer1 == scopedContainer2));
}

一个作用域创建的多个子作用域是否为相同作用域: False

2.4 作用域链

void Main()
{
	Container container = new Container();
	IResolverContext scopedContainer = container.OpenScope(name:"scopedContainer");
	IResolverContext nestedScopedContainer = scopedContainer.OpenScope(name:"nestedScopedContainer");
	IResolverContext nestedDoubleScopedContainer = nestedScopedContainer.OpenScope(name:"nestedDoubleScopedContainer");
	
	container.Dump(nameof(container));
	scopedContainer.Dump(nameof(scopedContainer));
	nestedScopedContainer.Dump(nameof(nestedScopedContainer));
	nestedDoubleScopedContainer.Dump(nameof(nestedDoubleScopedContainer));
}

作用域链 (container ) >> scopedContainer >> nestedScopedContainer >> nestedDoubleScopedContainer
容器虽然不包含任何作用域,但可作为作用域的父对象,因此这里把容器放到了作用域链的顶端

  • container
    在这里插入图片描述
  • scopedContainer
    • Parent 属性当前作用域的父作用域。如果当前作用域是通过容器直接创建的,则该属性的值为容器,否则属性的值为作用域。
    • Root 属性当前作用域的,其值为容器。
      在这里插入图片描述
  • nestedScopedContainer
    在这里插入图片描述
  • nestedDoubleScopedContainer
    在这里插入图片描述

2.5 延迟初始可能导致的问题

上述已经明确指出,如果在注册时指定了Reuse.Scope,那么就不能通过容器直接对服务进行解析,否则会报错。但是,如果在解析时使用了Func<T>Lazy<T>,将不会报错。但报错会延迟到使用解析的实例时,这是因为延迟初始化导致对象的初始化在使用实例时发生。

void Main()
{
	var container = new Container();
	container.Register<Car>(Reuse.Scoped);

	var carFactory = container.Resolve<Lazy<Car>>(); // 不发生异常
	Car car = null;
	using (var scopedContainer = container.OpenScope())
	{
		car = carFactory.Value;                      // 发生异常
	}
}

class Car
{
	public void DriveToMexico() { }
}

ContainerException: code: Error.NoCurrentScope;
message: No current scope is available: probably you are registering to, or resolving from outside of the scope.
Current resolver context is: container without scope.

3. 作用域上下文(ScopeContext)

作用域上下文是当前作用域及其嵌套作用域共享存储和跟踪机制。默认情况下,容器不包含任何作用域上下文,仅在其内部存储一个作用域。当一个容器提供了作用域上下文,将与作用域容器共享作用域上下文,是能在作用域外进行延迟解析称为可能。

void Main()
{
	var container = new Container(scopeContext: new AsyncExecutionFlowScopeContext());
	container.Register<Car>(Reuse.Scoped);
	
	var carFactory = container.Resolve<Lazy<Car>>();
	using (var scopedContainer = container.OpenScope())
	{
		var car = carFactory.Value;
		car.DriveToMexico();
	}
}

class Car
{
	public void DriveToMexico() { }
}

支持的作用域上下文如下:

  • AsyncExecutionFlowScopeContext 该作用域上下文能够跨域await/asunc的边界对作用域进行跟踪。如果确定用哪个,就用这个。
  • ThreadScopeContext在当前线程内共享作用域上下文
  • HttpContextScopeContextDryIoc.Web扩展中可用

4. 嵌套作用域

作用域在有无作用域上下文的情况下均可嵌套。

void Main()
{
	var container = new Container();
    container.Register<A>(Reuse.Scoped);

    // Three nested scopes
    var s1 = container.OpenScope();
    var s2 = s1.OpenScope();
    var s3 = s2.OpenScope();

	Console.WriteLine(s1.Resolve<A>() == s2.Resolve<A>()); // False
	Console.WriteLine(s1.Resolve<A>() == s3.Resolve<A>()); // False
	Console.WriteLine(s2.Resolve<A>() == s3.Resolve<A>()); // False
}

class A { }

DryIOC中,单例作用域不是作用域链中的一部分

在无边界的作用域上下文中,所有的嵌套作用域独立的存在与可用,所以可以在任何时间从任何嵌套的作用域内获取不同的实例。

下面为容器添加作用域上下文:

void Main()
{
	var container = new Container(scopeContext: new AsyncExecutionFlowScopeContext());
    container.Register<A>(Reuse.Scoped);

    // Three nested scopes
    var s1 = container.OpenScope();
    var s2 = s1.OpenScope();
    var s3 = s2.OpenScope();

	// 所有的实例时相同的,存储于最底层的作用域中,即s3
	Console.WriteLine(s1.Resolve<A>() == s1.Resolve<A>()); // True
	Console.WriteLine(s1.Resolve<A>() == s2.Resolve<A>()); // True
	Console.WriteLine(s2.Resolve<A>() == s3.Resolve<A>()); // True

	// 实例a实际上是从作用域s3中获得
	var a = s1.Resolve<A>();
	s3.Dispose();
	Console.WriteLine(a.IsDisposed);

	Console.WriteLine(a == s2.Resolve<A>());
	Console.WriteLine(s2.Resolve<A>() == s1.Resolve<A>());
}

class A : IDisposable
{
	public void Dispose() => IsDisposed = true;
	public bool IsDisposed { get; private set; }
}

与容器关联后的作用域上下文,共享一个当前作用域属性,该属性引用最深层次的嵌套作用域。当加深一层嵌套后,当前作用域属性被新的嵌套所取代,当销毁一个作用域,当前作用域将引用其父对象的作用域。

5. 作用域变种

5.1 Reuse.ScopedTo(name)

您可以给作用域打上一个唯一的名称标签。之后能够在嵌套作用域中选择指定名称的作用域进行服务实例解析。

void Main()
{
	var container = new Container();
    container.Register<Car>(Reuse.ScopedTo("top"));

    using (var s1 = container.OpenScope("top"))
    {
        var car1 = s1.Resolve<Car>();
        using (var s2 = s1.OpenScope())
        {
            var car2 = s2.Resolve<Car>();

            // Cars are the same despite that `car2` is resolved from the nested `s2`,
            // because it was specifically resolved from the matching name scope `s1`
            Console.WriteLine(car2 == car1); // True
        }
    }
}

class Car { }

当解析或注入一个使用Reuse.ScopeTo(name)的服务,DryIOC从当前作用域开始,沿着作用域链进行查找,直到找到对应的名称的作用域或者到达根作用域。

当一个作用域没有指定名称,其值为null

注意
作用域的名字需要为任意 Equals(object other)GetHashCode()方法可用的引用类型。

5.2 Reuse.InWebRequest 与 Reuse.InThread

  • Reuse.InWebRequest == Reuse.ScopedTo(specificName).
  • Reuse.InThread == Reuse.Scoped 区别是Reuse.InThreadThreadScopeContext中可用

5.3 Reuse.ScopedTo service

  • ScopedTo<TService>(object serviceKey = null)
  • ScopedToService(Type serviceType, object serviceKey = null)
    定义重用相同的依赖值。类似于将一个依赖的值幅值给一个变量,然后该变量传入到一个服务或其他依赖。
class Example_of_reusing_dependency_as_variable
{
    Foo Create()
    {
        var sub = new SubDependency();
        return new Foo(sub, new Dependency(sub));
    }

    class Foo
    {
        public Foo(SubDependency sub, Dependency dep) { }
    }

    class Dependency
    {
        public Dependency(SubDependency sub) { }
    }

    class SubDependency { }
} 

在上面的例子中,SubDependency的重用方式为Reuse.ScopedTo<Foo>()

void Main()
{
	var container = new Container();

    // `openResolutionScope` option is required to open the scope for the `Foo`
    container.Register<Foo>(setup: Setup.With(openResolutionScope: true));

    container.Register<Dependency>();
    container.Register<SubDependency>(Reuse.ScopedTo<Foo>());

    var foo = container.Resolve<Foo>();
    Console.WriteLine(foo.Sub == foo.Dep.Sub);
}

class Foo
{
	public SubDependency Sub { get; }
	public Dependency Dep { get; }
	public Foo(SubDependency sub, Dependency dep)
	{
		Sub = sub;
		Dep = dep;
	}
}

class Dependency
{
	public SubDependency Sub { get; }
	public Dependency(SubDependency sub)
	{
		Sub = sub;
	}
}

class SubDependency { }

5.4 Setup.UseParentReuse

这个选项允许依赖使用它的父服务或先祖服务的重用方式。万一,父服务或先祖服务是瞬态的,将被跳过,继续向上寻找,直到一个非瞬态的先祖被找到。如果所有的先祖都是瞬态的,或依赖在先祖的某下位置被Func<T>封装,这个依赖将是瞬态的。

如果先祖没有显示的指定重用方式,那么Reuse.DefaultReuse将被使用,且执行上述查找方式。

void Main()
{
	var container = new Container();
    container.Register<Mercedes>(Reuse.Singleton);
    container.Register<Dodge>();

    container.Register<Wheels>(setup: Setup.With(useParentReuse: true));

    // same Mercedes with the same Wheels
    var m1 = container.Resolve<Mercedes>();
	var m2 = container.Resolve<Mercedes>();
	Console.WriteLine(m1.Wheels == m2.Wheels); // 此处Wheel的父对象是Mercedes,将使用Mercedes的重用方式(Singleton)

	// different Dodges with the different Wheels
	var d1 = container.Resolve<Dodge>();
	var d2 = container.Resolve<Dodge>();
	Console.WriteLine(d1.Wheels == d2.Wheels); // 此处Wheel的父对象是Dodge,将使用Dodge的重用方式(Transient)
}

class Mercedes
{
	public Wheels Wheels { get; }
	public Mercedes(Wheels wheels)
	{
		Wheels = wheels;
	}
}

class Dodge
{
	public Wheels Wheels { get; }
	public Dodge(Wheels wheels)
	{
		Wheels = wheels;
	}
}

private class Wheels { }
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zhy29563

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值