新的C#Span <t>与ArraySegment <t>有何不同?

时间:2018-02-28 02:05:54

标签: c# .net-core

我无法概念化the new Span in C#的用法。

  1. 它取代了哪些构造? ArraySegment现在已经过时了吗?

  2. 以前不支持哪些功能?

  3. Span是C#Arrays的有效替代品吗?在哪些情况下是,在哪些情况下没有?

  4. 我何时使用ArraySegment而不是Span?

  5. 我试图了解我的编码风格需要如何更改才能有效利用新的Span。

3 个答案:

答案 0 :(得分:24)

Span<T>不会取代任何内容。这是增值。它提供了对连续内存段的类型安全视图,可以以多种不同方式分配:作为托管阵列,基于堆栈的内存或非托管内存。

ArraySegment<T>仅限于托管数组。您不能使用它来使用stackalloc包装在堆栈上分配的数据。 Span<T>允许您这样做。

ArraySegment<T>也不提供底层数组的只读视图。 ReadOnlySpan<T>为您提供了这一点。

Span<T>不应该替换数组。在一天结束时,它只是一个数据视图。必须以某种方式分配该数据,并且在托管世界中,在大多数情况下,分配将是数组分配。所以你仍然需要数组。

如果您希望代码能够操作的不仅仅是数组,那么您应该使用Span<T>。例如。考虑一个解析库。现在,为了允许它使用数组,堆栈分配的内存和非托管内存,它必须在API中为每个内容提供多个入口点,并使用不安全的代码来实际操作数据。它也可能需要公开一个基于string的API,供将数据分配为字符串的人使用。使用SpanReadOnlySpan,您可以将所有逻辑合并到基于Span的单个解决方案,该解决方案将适用于所有这些方案。

Span<T>绝对不是每个人都经常使用过的东西。它是.NET框架的一个高度专业化的部分,主要用于库作者和非常高性能的关键场景。例如。 Kestrel,ASP.NET Core背后的Web服务将从转移到Span<T>获得很多性能优势,例如解析请求可以使用Span<T>和堆栈分配的内存来完成,这对GC没有任何压力。但是,您编写基于ASP.NET Core的网站和服务并不一定非必须使用它。

答案 1 :(得分:5)

MSDN Magazine开始:Span的定义方式使得操作可以像在数组上一样高效:索引到span中不需要计算来确定指针的起点及其起始偏移量,如ref字段本身已经封装了两者。 (相比之下,ArraySegment有一个单独的偏移字段,使得索引和传递它的成本更高。)

此外,当Array实现IEnumerable时,Span不会。

答案 2 :(得分:1)

在决定是否使用Span时,也要考虑到C#中 ref结构的限制:

https://docs.microsoft.com/en-us/dotnet/api/system.span-1?view=netcore-2.2

  

Span是一个 ref结构,它在堆栈上分配,而不是分配   在托管堆上。 引用结构类型对   确保不能将它们提升为托管堆,包括   他们不能装箱,他们不能分配给类型的变量   动态或任何接口类型的对象,它们不能是   引用类型,它们不能在等待和收益中使用   边界。另外,调用两个方法Equals(Object)和   GetHashCode,引发NotSupportedException。

     

重要

     

因为它是仅堆栈类型,所以Span不适合许多   需要在堆上存储对缓冲区的引用的方案。这个   例如,对于进行异步方法调用的例程,它是正确的。   在这种情况下,您可以使用免费的System.Memory和   System.ReadOnlyMemory类型。

     

对于表示不可变或只读结构的跨度,请使用   System.ReadOnlySpan。

https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/ref?view=netcore-2.2#ref-struct-types

  

将ref修饰符添加到结构声明中可定义实例   这种类型的必须堆栈分配。换句话说,   这些类型永远不能作为另一个的成员在堆上创建   类。此功能的主要动机是跨度和相关性   结构。

     

将引用结构类型保留为堆栈分配变量的目标   引入了几个规则,这些规则由编译器针对所有引用结构强制执行   类型。

     
      
  • 您不能装箱引用结构。
  •   
  • 您不能将引用结构类型分配给类型为object,dynamic或任何接口类型的变量。
  •   
  • 引用结构类型无法实现接口。
  •   
  • 您不能将引用结构声明为类或普通结构的成员
  •   
  • 您不能在异步方法中声明作为引用结构类型的局部变量。您可以在返回Task,Task或类似Task的类型的同步方法中声明它们。
  •   
  • 您不能在迭代器中声明引用结构局部变量。
  •   
  • 您无法在lambda表达式或本地函数中捕获引用结构变量。
  •   
  • 这些限制确保您不会意外地将引用结构提升为托管堆。
  •   
     

您可以组合使用修饰符以将结构声明为只读引用。一种   只读ref结构结合了ref的优点和限制   结构体和只读结构体声明。