查看原文
其他

传说中的孪生兄弟? Memory and Span

DotNet 2021-09-23

(给DotNet加星标,提升.Net技能

转自:句幽
cnblogs.com/uoyo/p/12218998.html

【五分钟的.NET】是一个利用您的碎片化时间来学习和丰富.NET知识的博文系列。


它所包含了.NET体系中可能会涉及到的方方面面,比如C#的小细节,AspnetCore,微服务中的.NET知识等等。


5min+不是超过5分钟的意思,"+"是知识的增加。so,它是让您花费5分钟以下的时间来提升您的知识储备量。


正文


在上一篇文章:《闪电光速拳?.NET Core 中的Span》 中我们提到了在.NET Core 2.x 所新增的一个类型:Span。


它与咱们传统使用的基础类型相比具有超高的性能,原因是它减少了大量的内存分配和数据量复制,并且它所分配的数据内存是连续的。


但是您会发现它无法用在我们项目的某些地方,它独特的 ref结构 使它没有办法跨线程使用、更没有办法使用Lambda表达式。




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();
//.....
}



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。




除了 Memory<T>之外,还可以使用 System.ReadOnlyMemory<T> 来表示不可变或只读内存。


这是MSDN给出来的解释,不是我乱编的哈!(虽然和我们上面猜的一模一样)


接下来,我们来看看他们到底有多像:



好吧,为了做该图我已经使用了美工必杀器 - ps


有没有发现,除了名字之外,好像其它的都一模一样😱。甚至直接连注释都懒得改了。


一样却又不一样


既然作为孪生兄弟,必然有一些共通之处。而Memory作为对Span的增强(应该也算不算增强吧),那么内部的实现可能很多会与Span相似。


是的,查看Memory的源代码您就会发现,它的内部某些方法就是通过Span来实现的:


public readonly struct Memory<T>
{
public void CopyTo(Memory<T> destination) => Span.CopyTo(destination.Span);
public T[] ToArray() => Span.ToArray();
}


有关Memory的源代码,您可以点此查看:the source code of Memory

https://github.com/dotnet/runtime/blob/master/src/libraries/System.Private.CoreLib/src/System/Memory.cs。



如何使用


来吧,修改上面的Span会在Task中报错的例子:


public async Task MemoryCanInLambda(Memory<string> buffer)
{
await Task.Factory.StartNew(() =>
{
buffer.Trim("s");
});
}


此时我们就可以在异步中使用Memory了,采用连续内存+指针级别的操作方案来操作数据内容,岂不爽歪歪?


异步的数据交由Memory,同步的数据交由Span,ForExample:


static async Task<int> ChecksumReadAsync(Memory<byte> buffer, Stream stream)
{
int bytesRead = await stream.ReadAsync(buffer);
return Checksum(buffer.Span.Slice(0, bytesRead));
// Or buffer.Slice(0, bytesRead).Span
}
static int Checksum(Span<byte> buffer) { ... }


正是由于Span和Memory带来的巨大性能优化,所以.NET Core的开发者们做了一件非常疯狂的事:为.NET的库添加了数百个重载方法。比如,您现在可以看到我们经常使用的Int.Parse方法居然支持了Span,它的签名是酱紫:


public static Int32 Parse(ReadOnlySpan<char> s, NumberStyles style = NumberStyles.Integer, [NullableAttribute(2)] IFormatProvider? provider = null);


除此之外,还有long,double…………甚至连Guid和DateTime都有这样的重载。

还有其它常用的各种类也开始支持以Span作为参数的重载方法了,比如Random、StringBuilder等。


public StringBuilder Append(ReadOnlySpan<char> value);



.NET Core正在为它的实现和使用做巨大的适配工作,C# 从7.x 开始就不断对异步操作和内存分配进行优化,这或许也为我们未来.NET的发展给了一点点提示。加油,伟大的开发人员们。


推荐阅读  点击标题可跳转

C#录制视频框架

闪电光速拳? .NET Core中的Span

ASP.NET开源导入导出库Magicodes.IE导出Pdf


看完本文有收获?请转发分享给更多人

关注「DotNet」加星标,提升.Net技能 

好文章,我在看❤️

: . Video Mini Program Like ,轻点两下取消赞 Wow ,轻点两下取消在看

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存