- A+
> 过年啦,写个短点的。同时,提前给大家拜个年。
 
总有小伙伴们跑过来讨论关于Span和Memory的使用,眼瞅是最近关于Span的文章有点多,看飞了。
今天写这个,就是往回拉一拉。
写之前,先声明一下。这些内容是我自己使用的一些经验,并不代表这些类的全部内容就是这些,只是说,我是这么用的,而且用得很好。
## 1. Span
Span在我的概念中,就是一个快速的同步访问器。
就这么简单。
Span很快。在我前边关于Span的文章中分析过,可以移步【[传送门](https://www.cnblogs.com/tiger-wang/p/14029853.html)】去看。而且,它与foreach一起使用也很快,主要是因为Span的GetEnumerator使用了引用返回。
你看,Span本身就被设计成了一个非常快的东西。
同时,Span是同步的。也就是说,它没有提供任何异步的方法和属性。
说到为什么Span是同步的,这倒是一个问题。我们需要从根上来找找。Span背后的连续内存块,主要来自于以下几个方面:
* 数组的切片
* `Memory<T>`
* 非托管指针`void*`
* `stackalloc`
其中,第一个是堆上分配的数组的一部分。第二个是基于连续内存的。第三个非托管`void*`本身就是同步的。
第四个单独说一下。stackalloc提供的是在线程的堆栈上分配内存。如果Span可以使用异步,会导致一个线程可以访问另一个线程的堆栈。显然这是不安全和不合理的。所以,保持Span同步是必须的。
所以,Span就是一个性能非常好的,针对连续内存的同步访问器。
## 2. Memory
Memory,就是一个实际的内存块。
与Span不同,Memory可以在异步流中使用,同时,它还提供了获取同步访问器的方法`Memory<T>.Span()`。
Memory可以有多种来源,例如:
* 数组切片
* `MemoryMarshal`的各种`Create`方法,例如`MemoryMarshal.CreateFromPinnedArray()`这样的。
第一个是最基本的用法,从数组T[]中取一个切片成为Memory。
第二个方法会复杂一些,用了一个特殊的方法来创建Memory。像上边的例子,CreateFromPinnedArray用了一个已经固定的数组。在Dotnet中,可以通过固定一个对象,来禁止GC移动对象。这在将Memory传递给非托管对象时非常有用。
总之,Memory就是一个实际的内存块。这个内存块可以被用到任何地方,并可以使用它的同步访问器Span进行访问。
## 3. ReadOnlyX
印象中有三种:ReadOnlySpan、ReadOnlyMemory、ReadOnlySequence。
没什么特别的,就是ReadOnly,只读啦。
前两个,ReadOnlySpan、ReadOnlyMemory,就是Span和Memory对应的只读对象。
## 4. ReadOnlySequence
ReadOnlySequence也不算复杂,就是一个ReadOnlyMemory元素的序列。
基于操作系统的内存管理,有时候Memory不是连续的,可能会分片段,所以就需要有个结构来表示一个Memory链/Memory列表类似的序列。这是ReadOnlySequence的由来,而它本身也是一个ReadOnlyMemory的列表。
同时,它也提供了一些属性来优化序列中包含一个元素的情况:
* IsSingleSegment,用来快速检查是否只包含一个内存项
* FirstSpan,该速访问ReadOnlySpan访问器的第一个内存项
因此虽然被定义为序列,但处理单个元素,例如单个Span或Memory也容易很多。
 
这就是今天的全部内容了。
有没有跟你用的不一样?
 
文章最后,再次祝大家牛年大吉,万事胜意~
 
<table border="0">
<tbody>
<tr>
<td><img src="https://img2020.cnblogs.com/blog/907112/202005/907112-20200527230728396-985375280.jpg" alt="Dotnet中Span, Memory和ReadOnlySequence之浅见" alt="" width="160" height="160" /></td>
<td>
<p>微信公众号:老王Plus</p>
<p>扫描二维码,关注个人公众号,可以第一时间得到最新的个人文章和内容推送</p>
<p>本文版权归作者所有,转载请保留此声明和原文链接</p>
</td>
</tr>
</tbody>
</table>