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
在当前线程内共享作用域上下文HttpContextScopeContext
在DryIoc.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.InThread
仅ThreadScopeContext
中可用
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 { }