在上一篇文章:《閃電光速拳? .NetCore 中的Span》中我們提到了在.net core 2.x 所新增的一個類型:Span。但是您會發現它無法用在我們項目的某些地方,它獨特的 ref結構 使它沒有辦法跨線程使用、更沒有辦法使用Lambda表達式。所以,這個時候如果我們又想跨線程操作數據又想獲... ...
系列介紹
【五分鐘的dotnet】是一個利用您的碎片化時間來學習和豐富.net知識的博文系列。它所包含了.net體系中可能會涉及到的方方面面,比如C#的小細節,AspnetCore,微服務中的.net知識等等。
5min+不是超過5分鐘的意思,"+"是知識的增加。so,它是讓您花費5分鐘以下的時間來提升您的知識儲備量。
正文
在上一篇文章:《閃電光速拳? .NetCore 中的Span》 中我們提到了在.net core 2.x 所新增的一個類型:Span。
它與咱們傳統使用的基礎類型相比具有超高的性能,原因是它減少了大量的記憶體分配和數據量複製,並且它所分配的數據記憶體是連續的。
但是您會發現它無法用在我們項目的某些地方,它獨特的 ref結構 使它沒有辦法跨線程使用、更沒有辦法使用Lambda表達式。
特別是在AspNetCore中,咱們會使用到大量的非同步操作方法。“所以,這個時候如果我們又想跨線程操作數據又想獲得類似Span這樣的性能怎麼辦呢?” 上一篇文章我們留下了這樣的一個問題,所以現在就是到了還願的時候了。它就是與Span一起發佈的孿生兄弟: Memory。
獅子座和射手座黃金聖鬥士同樣具備超越光速的能力
什麼是Memory
那什麼是Memory呢?不妨我們先來猜測一下,它的結構是什麼樣子。畢竟它是Span的孿生兄弟,而Span的結構我們在前面就瞭解過了:
public readonly ref struct Span<T>
{
public void Clear();
public void CopyTo([NullableAttribute(new[] { 0, 1 })] Span<T> destination);
public void Fill(T value);
public Enumerator GetEnumerator();
public Span<T> Slice(int start, int length);
public T[] ToArray();
public override string ToString();
//.....
}
當時我們說Span有各種缺陷的原因是由於它獨特的 ref struct 關鍵字所導致的,導致它無法拆箱裝箱、無法書寫Lambda、無法跨線程等。但是它兄弟卻可以剋服缺點,所以我們想想它會和Span在聲明上有哪些差距呢? 是的,您可能已經想到了:它不會有 ref 關鍵字了。
所以,我們看到它的內部結構就是醬紫的:
public readonly struct Memory<T>
{
public static Memory<T> Empty { get; }
public bool IsEmpty { get; }
public int Length { get; }
public Span<T> Span { get; }
public void CopyTo([NullableAttribute(new[] { 0, 1 })] Memory<T> destination);
public MemoryHandle Pin();
public Memory<T> Slice(int start, int length);
public T[] ToArray();
public override string ToString();
}
和我們猜想的一樣。它少了ref關鍵字,內部方法也和Span差不多(同樣擁有CopyTo,Slice等),但是還是有一些差異,比如多了Pin方法,Span屬性等。
被聲明為ref struct的結構,叫做“ByRefLike”。所以在我們在進行反射的時候,我們使用Type會看到有這樣一個屬性:IsByRefLike。
好像有點超綱了哈(>人<;)
按照MSDN給出的解釋:
該結構是使用中的C# ref struct 關鍵字聲明的。 不能將類似 byref 的結構的實例放置在托管堆上。
所以這也是為什麼上一篇文章說的:Span只能放置在記憶體棧中的原因。
那麼反過來想,沒有了ref關鍵字之後。Memory是不是就可以放置在托管堆上了呢?是不是就可以進行拆裝箱,克隆副本供其它線程的記憶體棧使用了呢? 好吧,可能是這樣。所以這也許就是它能夠被允許跨線程使用的原因吧。
進行到了這一步,那我們再回過頭來想想Memory是什麼呢? 其實現在我們心裡其實都已經有個底了:
與 Span<T>一樣,Memory<T> 表示記憶體的連續區域。 但 Span<T>不同,Memory<T> 不是ref 結構。 這意味著 Memory<T> 可以放置在托管堆上,而 Span<T> 不能。 因此,Memory<T> 結構與 Span<T> 實例沒有相同的限制。 具體而言:
- 它可用作類中的欄位。
- 它可跨 await 和 yield 邊界使用。
除了 Memory<T>之外,還可以使用 System.ReadOnlyMemory<T> 來表示不可變或只讀記憶體。
這是MSDN給出來的解釋,不是我亂編的哈