阅读 Abp 源码的过程中,自己也学习到了一些之前没有接触过的知识。在这里,我在这儿针对研究学习 Abp 框架中,遇到的一些值得分享的知识写几篇文章。如果有什么疑问或者问题,欢迎大家评论指正。
在本篇主要是 Scoped 范围与 using 语句块的使用。using 语句块大家一定都不陌生,都是与非托管对象一起存在的,它有一个特性就是在 using 语句块结束的时候会调用对象的 IDispose.Dispose() 方法。一般我们会在非托管类型的 Dispose() 方法内部进行资源的释放,类似于 C 语言的 free() 操作。
例如下面的代码:
public void TestMethod() { using(var waitDisposeObj = new TestClass()) { // 执行其他操作 xxx } // 出了语句块之后就,自动调用 waitDisposeObj 的 Dispose() 方法。 }可以看到上面的例子,using 语句块包裹的就是一个范围 (Scoped)。其实这里可以延伸到依赖注入的概念,在依赖注入的生命周期当中有一个 Scoped 的生命周期。(PS: 需要了解的可以去阅读我的 这篇文章)
一个 Scoped 其实就可以看作是一个 using 语句块包裹的范围,所有解析出来的对象在离开 using 语句块的时候都应该被释放。
例如下面的代码:
public void TestMethod() { using(var scopedResolver = new ScopedResolver()) { var a = scopedResolver.Resolve<A>(); var b = scopedResolver.Reslove<B>(); } // 出了语句块之后 a b 对象自动释放 }其实这里也是利用了 using 语句块的特性,在 ScopedResolver 类型的定义当中,也实现了 IDisopse 接口。所以在 using 语句块结束的时候,会自动调用 ScopedResovler 的 Dispose() 方法,在这个方法内部则对已经解析出来的对象调用其 Dispose() 进行释放。
二、分析 2.0 释放委托也是不知道叫什么标题了,这玩意儿是 Abp 封装的一个类型,它的作用就是在 using 语句块结束的时候,执行你传入的委托。
使用方法如下:
var completedTask = new DisposeAction(()=>Console.WriteLine("using 语句块结束了。")); using(completedTask) { // 其他操作 } // 执行完成之后会调用 completedTask 传入的委托。根据上述用法,你也应该猜出来这个 DisposeAction 类型的定义了。该类型继承了 IDispose 接口,并且在内部有一个 Action 字段,用于存储构造函数传入的委托。在执行 Dispose() 方法的时候,执行传入的委托。
public class DisposeAction : IDisposable { public static readonly DisposeAction Empty = new DisposeAction(null); private Action _action; public DisposeAction([CanBeNull] Action action) { _action = action; } public void Dispose() { // 防止在多线程环境下,多次调用 action var action = Interlocked.Exchange(ref _action, null); action?.Invoke(); } } 2.1 统一对象释放统一对象释放是 Abp 当中的另一种用法,其实按照 Abp 框架的定义,叫做 ScopedResolver(范围解析器)。顾名思义,通过 ScopedResolver 解析出来的对象,都会在 using 语句块结束之后统一进行销毁。
IScopedIocResolver 接口继承自 IIocResolver 和 IDisposable 接口,它的本质就是作为 Ioc 解析器的一种特殊实现,所以它拥有所有 Ioc 解析器的方法,这里就不再赘述。
它的实现也比较简单,在其内部有一个集合维护每一次通过 IIocResolver 解析出来的对象。在 Dispose() 方法执行的时候,遍历这个集合,调用 Ioc 解析器的 Release() 方法释放对象并从集合中删除对象。下面就是实现的简化版:
public class ScopedIocResolver : IScopedIocResolver { private readonly IIocResolver _iocResolver; private readonly List<object> _resolvedObjects; public ScopedIocResolver(IIocResolver iocResolver) { _iocResolver = iocResolver; _resolvedObjects = new List<object>(); } // 解析对象 public object Resolve(Type type) { var resolvedObject = _iocResolver.Resolve(type); // 添加到集合,方便后续释放 _resolvedObjects.Add(resolvedObject); return resolvedObject; } public void Release(object obj) { // 从集合当中移除 _resolvedObjects.Remove(obj); // 通过 Ioc 管理器释放对象 _iocResolver.Release(obj); } public void Dispose() { // 遍历集合,释放对象 _resolvedObjects.ForEach(_iocResolver.Release); } }通过 IScopedResolver 解析出来的对象,在 using 语句块结束的时候都会被释放,免去了我们每次手动释放的操作。
2.2 临时值变更暂时想不到一个好一点的标题,暂时用这个标题代替吧。这里以 Abp 的一段实例代码为例,在有的时候我们可能当前的用户没有登录,所以在 IAbpSession 里面的 UserId 等属性肯定是为 NULL 的。而 IAbpSession 在设计的时候,这些属性是不允许更改的。
那么我们有时候可能会临时更改 IAbpSession 里面关于 UserId 的值怎么办呢?
这个时候可以通过 IAbpSession 提供的一个 IDisposable Use(int tenantId, long? userId, string userCode) 进行临时更改。他拥有一个 Use() 方法,并且返回一个实现了 IDispose 接口的对象,用法一般是这样:
public void TestMethod() { using(AbpSession.Use(1,2,"3")) { // 内部临时更改了 AbpSession 的值 } // using 语句块结束的时候,调用 Use 返回对象的 Dispose 方法。 }