关于 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. 活动预告 | 中国数据库联盟(ACDU)中国行定档深圳,一起揭秘数据库前沿技术

    在当今数字化时代,数据库是各行各业中最核心的信息管理系统之一.随着技术的飞速发展,数据库领域也不断涌现出新的前沿技术和创新应用.数据库运维和开发人员需要紧跟前沿技术,才能保持竞争力,并实现更高效.更智 ...

  2. python中字典的运算

    问题: 如何查找在两个字典中相同的键.值元素? dict1 = {'a': 1, 'b': 2, 'c': 3} dict2 = {'a': 10, 'y': 11,'b': 2} dict1.key ...

  3. 使用nacos上传配置文件报错

    1.使用nacos导入配置文件报错:未读取到合法数据,请检查导入的数据文件. 对比在naocs server中导出的文件,发现是少了一级目录.需要创建一个文件夹,名称为组的名称.因为在nacos上传文 ...

  4. 基于 KubeSphere 的开源微服务开发平台 Pig 最佳实践

    作者:何昌涛,北京北大英华科技有限公司高级 Java 工程师,云原生爱好者. 前言 近年来,为了满足越来越复杂的业务需求,我们从传统单体架构系统升级为微服务架构,就是把一个大型应用程序分割成可以独立部 ...

  5. ajax下载二进制文件(导出Excel)

    var url = 'http://127.0.0.1'; var xhr = new XMLHttpRequest(); xhr.open('GET', url, true); // 也可以使用PO ...

  6. Sealos 基础教程:Sealos Devbox 的架构原理解析

    今天这篇文章咱们来聊一聊 Sealos Devbox 到底是怎么设计的,据说隔壁老奶奶最喜欢看这种有技术深度的文章了. Devbox 返璞归真,把开发者的开发精力放到开发中去,真正做到了摈弃复杂的 C ...

  7. 3.9 Linux查看目录中的文件(ls命令)

    通过学习cd 和 pwd 命令,相信读者已经能够在庞大的 Linux 文件系统中,随心所欲地游荡并确定自己所在的位置了.本节继续来学习,如何知道某目录中存放了哪些文件或子目录. ls 命令,list ...

  8. ABP - 菜单配置(导航栏选中高亮,高亮并定位当前标题)

    配置一个如上图所示的菜单: 1.打开文件NavigationProvider.cs 添加如下代码(如下图所示) .AddItem(new MenuItemDefinition( PageNames.A ...

  9. php 版本升级后需要对代码进行兼容性检测

    来到需要检测代码的目录下 需要提前安装 docker 执行 docker run --rm -v $(pwd):/app vfac/php7compatibility 7.3 . --ignore=v ...

  10. 鸿蒙NEXT开发案例:二维码的生成与识别

    [引言] 在本篇文章中,我们将探讨如何在鸿蒙NEXT平台上实现二维码的生成与识别功能.通过使用ArkUI组件库和相关的媒体库,我们将创建一个简单的应用程序,用户可以生成二维码并扫描识别. [环境准备] ...