关于 Span 的一切:探索新的 .NET 明星

https://docs.microsoft.com/en-us/archive/msdn-magazine/2018/january/csharp-all-about-span-exploring-a-new-net-mainstay

内容列表:

想象一下你正在发布一个特别的排序算法程序,它可以在内存中就地处理数据。你会希望发布一个获得一个数组参数,并提供在数组之上操作 T[] 的实现。如果调用方可以获得这个数组,并且是希望对整个数组进行排序,那么这种方式特别棒。但是,如果调用方仅仅希望对数组的部分进行排序呢,你可能又会出提供一个重载的实现,通过 offset 和 count 参数来支持。不过,如果你又希望支持内存中不是数组的数据,比如说,而是来自原生代码呢?或者是在堆栈上的数据,你只有指向它的指针和长度呢?你又如何开发你的排序方法来操作此类任意的内存区域,而且仍然与处理整个数组,或者数组的子集一样好呢?还要考虑到处理托管数组与非托管数组一样好呢?

或者,我们看另一个例子。你正在实现一个对 System.String 的处理。例如是一个特别的解析方法。你希望获得一个字符串参数,然后提供提供处理字符串的实现。但是,如果你希望还要支持处理该字符串的子集呢?String.Substring() 方法可以用来抽取出感兴趣的一部分字符串,但是这会牵涉到昂贵的操作,导致字符串分配和内存复制。你也可以这样做,如在数组示例中那样,通过一个 offset 和 count 参数来处理。不过,如果调用方并没有得到这个字符串,而是得到了一个 char[] 数组呢?或者调用方得到的是指针 char* 呢?或者是通过调用 stackalloc 使用栈空间呢?或者是调用原生代码得到的结果呢?你又如何开发你的解析方法,不需要强制调用者做任何内存分配或者复制的一种方式呢?并且仍然一致良好地处理各种输入类型,比如字符串、char[] 和 char* 呢?

在这两种场景下,你可能可以使用 unsafe 代码和指针来完成,提供接受指针和长度的输入。不过,这样丢掉了 .NET 的核心的安全保证,打开了问题之门,比如缓冲区溢出,访问冲突等等,这些对大多数 .NET 开发者已经过去的问题。它还引入了额外的性能惩罚,比如需要在处理过程中钉住托管对象,以便你获得的指针保持有效。基于底层不同的数据类型,并不总是可以获得指针。

对于这个谜题的答案就是,Span <T>

什么是 Span<T>?

System.Span<T> 是来自 .NET 核心的新的值类型。它支持表示内存中任意一段连续的区间,不管这段内存属于一个托管对象,还是通过原生代码进行互操作得到,或者是被分配在堆栈上。尽管这样还提供了类似数组操作的高性能的安全访问。

例如,你可以从数组来创建 Span<T>:

var arr = new byte[10];
Span<byte> bytes = arr; // Implicit cast from T[] to Span<T>

从这里开始,你可以简单且高效地利用 Span 的 Slice() 重载方法,创建一个 Span 来表示/指向该数组的一个子集。通过它,你可以通过下标来读、写源数组相关的部分。

Span<byte> slicedBytes = bytes.Slice(start: 5, length: 2);
slicedBytes[0] = 42;
slicedBytes[1] = 43;
Assert.Equal(42, slicedBytes[0]);
Assert.Equal(43, slicedBytes[1]);
Assert.Equal(arr[5], slicedBytes[0]);
Assert.Equal(arr[6], slicedBytes[1]);
slicedBytes[2] = 44; // Throws IndexOutOfRangeException
bytes[2] = 45; // OK
Assert.Equal(arr[2], bytes[2]);
Assert.Equal(45, arr[2]);

如前所述,Span 还提供了访问数组子集的一种方式。它也可以用来指向堆栈上的数据。例如:

Span<byte> bytes = stackalloc byte[2]; // Using C# 7.2 stackalloc support for spans
bytes[0] = 42;
bytes[1] = 43;
Assert.Equal(42, bytes[0]);
Assert.Equal(43, bytes[1]);
bytes[2] = 44; // throws IndexOutOfRangeException

更为方便的是,它可以用来指向任意的指针和长度,例如通过本地堆分配的内存,例如:

IntPtr ptr = Marshal.AllocHGlobal(1);
try
{
  Span<byte> bytes;
  unsafe { bytes = new Span<byte>((byte*)ptr, 1); }
  bytes[0] = 42;
  Assert.Equal(42, bytes[0]);
  Assert.Equal(Marshal.ReadByte(ptr), bytes[0]);
  bytes[1] = 43; // Throws IndexOutOfRangeException
}
finally { Marshal.FreeHGlobal(ptr); }

Span<T> 的索引器借助于被称为 ref return 的从 C# 7.0 引入的 C# 特性。该索引器使用 ref T 返回类型定义。它提供了类似于索引数组的语法,返回实际存储位置的引用,而不是在该位置内存的复制品。

public ref T this[int index] { get { ... } }

通过该示例,该 ref-returning 索引器的影响显而易见,例如与 List 索引器相比,它不是 ref returning 的。下面是一个示例:

struct MutableStruct { public int Value; }
...
Span<MutableStruct> spanOfStructs = new MutableStruct[1];
spanOfStructs[0].Value = 42;
Assert.Equal(42, spanOfStructs[0].Value);
var listOfStructs = new List<MutableStruct> { new MutableStruct() };
listOfStructs[0].Value = 42; // Error CS1612: the return value is not a variable

Span<T> 的一种变体,被称为 System.ReadOnlySpan<T>,支持只读访问。该类型与 Span<T> 类似,除了借助于 C#7.2 中引入的 ref readonly T 特性,而不是 ref T。使得它可以处理不变的数据类型,比如 System.String。ReadOnlySpan<T> 使得可以非常高效地处理字符串切片,而不需要分配或者复制内存,例如:

string str = "hello, world";
string worldString = str.Substring(startIndex: 7, length: 5); // Allocates
ReadOnlySpan<char> worldSpan =
str.AsSpan().Slice(start: 7, length: 5); // No allocation
Assert.Equal('w', worldSpan[0]);
worldSpan[0] = 'a'; // Error CS0200: indexer cannot be assigned to

除了这些已经介绍的特性,Span 还提供了多种优点。例如,Span 支持类型转换符号,意味着你可以强制一个 Span<byte> 到 Span<int> ( 这里 Span<int> 的 0 下标映射到 Span<byte> 第一个 4 字节 )。这样如果你读取 bytes 缓冲区,你可以安全且高效地将它传递给操作一组字节,例如 int 类型。

关于 Span 的一切:探索新的 .NET 明星: 1 Span<T> 是什么?的更多相关文章

  1. Android开发艺术探索——新的征程,程序人生路漫漫!

    Android开发艺术探索--新的征程,程序人生路漫漫! 偶尔写点东西分享,但是我还是比较喜欢写笔记,看书,群英传看完了,是学到了点东西,开始看这本更加深入Android的书籍了,不知道适不适合自己, ...

  2. Dual Path Networks(DPN)——一种结合了ResNet和DenseNet优势的新型卷积网络结构。深度残差网络通过残差旁支通路再利用特征,但残差通道不善于探索新特征。密集连接网络通过密集连接通路探索新特征,但有高冗余度。

    如何评价Dual Path Networks(DPN)? 论文链接:https://arxiv.org/pdf/1707.01629v1.pdf在ImagNet-1k数据集上,浅DPN超过了最好的Re ...

  3. 探索新冠肺炎(COVID-19)对全球航班的影响

    Cesium中文网:http://cesiumcn.org/ | 国内快速访问:http://cesium.coinidea.com/ 随着今天从欧洲到美国的旅行限制生效,以及为了减缓新冠病毒的传播更 ...

  4. span 右浮动折行 解决ie6/7中span右浮动折行问题

    A floated box is shifted to the left or right until its outer edge touches the containing block edge ...

  5. span 右浮动折行 解决ie6/7中span右浮动折行问题

    RM8005: IE6 IE7 IE8(Q) 中行内元素后相邻的浮动元素在某些情况下会折行放置在之前行内元素所在行框的底部 标准参考 W3C CSS 2.1 规范文档里对于浮动元素与非浮动行内元素相邻 ...

  6. “display:block-inline形式的Span或Div中添加文字后,导致Span或Div排版掉落、错位”的原因及解决方法

    最近在使用3个span(或div)制作带圆角边框的按钮时,按照常识,把span的display设置成inline-block,这样就可以设置span的width和height了,很爽的~ 可是当我在中 ...

  7. 读bootstrap2.3.2有感1

    起步: 下载编译好的bootstrap2文件,百度新版jquery.js,并复制html模版(hello world)放置在同一目录,然后看了下官网上的范例网站,心里还是很激动啊~ <!DOCT ...

  8. [bootstrap] 栅格系统和布局

    1.简介 栅格系统(grid systems),也称为“网格系统”,运用固定的格子设计版面布局,风格工整简洁.是从平面栅格系统演变而来. Bootstrap建立在12列栅格系统.布局.组件之上.以规则 ...

  9. Boostrap栅格系统

    Boostrap排版.链接样式设置了基本的全局样式.分别是:为body元素设置 布局容器:Bootstrap需要为页面内容和栅格系统包裹一个:container容器.Bootstrap提供了两个作此用 ...

  10. HTML+CSS实现页面

    使用HTML和CSS实现以下页面: 抽屉首页 个人博客首页 小米官网首页 登录注册页面 一.抽屉首页 1.实现目标:https://dig.chouti.com/ 2.代码: HTML: <!- ...

随机推荐

  1. 简单粗暴的实现 Blazor Server 登录鉴权

    既然是简单粗暴,那么就不用关心诸如 IDentityServer4,OAuth 之类的组件,也不使用 AuthenticationStateProvider.IAuthService, razor 页 ...

  2. 【USB3.0协议学习】Topic3·三种Reset Events分析

    USB3.0中的三种Reset Events 1. PowerOn Reset PowerOn Reset被用来代指上电复位,当一个device接入到root hub或者外置hub的时候,该devic ...

  3. Camera 冷启动阶段分解

    目录 一.Camx trace 调试开关设置 1.设置 camxoverridesettings trace开关 2. 重启后设置开启camx trace 开关 二.Camera 冷启动阶段分解分析 ...

  4. 新建 Blazor 项目 WebAssembly

  5. typeof typeof 'texs'是什么类型

    typeof '12' 返回  'string' 是字符串类型  :

  6. keycloak~token有效期与session有效期

    一 refresh_token刷新access_token Keycloak会话管理中,获取到accessToken和refreshToken后,基于accessToken交换用户数据或者参与Keyc ...

  7. 基于 KubeSphere 的应用容器化在智能网联汽车领域的实践

    公司简介 某国家级智能网联汽车研究中心成立于 2018 年,是担当产业发展咨询与建议.共性技术研发中心.创新成果转化的国家级创新平台,旨在提高我国在智能网联汽车及相关产业在全球价值链中的地位. 目前着 ...

  8. 分享一个很好用的代理转发工具:rinetd

    rinetd介绍: 安装与使用:https://zhuanlan.zhihu.com/p/530875131 注意事项: 1.如果发现配置中的端口在进程中没找到,那就是配置填写错误导致的,笔者就遇到过 ...

  9. VS2019插件更新慢的解决办法

    VS2019更新巨慢,可以尝试通过以下几个方法解决: 1. 关闭IPV6 关闭IPV6:Win10怎么样禁用IPV6 如何关闭IPV6协议-百度经验 (baidu.com) 2. 选择最快的DNS 百 ...

  10. GaussDB: db2->gaussdb 函数转换

    一.db2->gaussdb函数转换 问题描述:使用GaussDB替代DB2的方案,使用起来还是有些差别,做一下函数的映射转换.   DB2写法 GaussDB改写语法 日期函数 days(OU ...