【问题标题】:How is the new C# Span<T> different from ArraySegment<T>?新的 C# Span<T> 与 ArraySegment<T> 有何不同?
【发布时间】:2018-02-28 02:05:54
【问题描述】:

我在概念化the new Span<T> in C# 的用法时遇到了麻烦。

  1. 它取代了什么构造? ArraySegment 现在过时了吗?

  2. 它启用了哪些以前没有的功能?

  3. Span 是 C# 数组的有效替代品吗?哪些情况是,哪些情况不是?

  4. 我什么时候会使用 ArraySegment 而不是 Span

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

【问题讨论】:

标签: c# .net-core


【解决方案1】:

Span&lt;T&gt; 不会替代任何东西。它是增值的。它提供了对连续内存段的类型安全视图,可以通过多种不同方式分配这些内存段:作为托管数组、基于堆栈的内存或非托管内存。

ArraySegment&lt;T&gt; 仅限于托管阵列。您不能使用它来包装使用stackalloc 在堆栈上分配的数据。 Span&lt;T&gt; 允许您这样做。

ArraySegment&lt;T&gt; 也不提供底层数组的只读视图。 ReadOnlySpan&lt;T&gt; 为您提供。

Span&lt;T&gt; 不应该替换数组。归根结底,它只是对数据的看法。必须以某种方式分配该数据,并且在托管世界中,该分配在大多数情况下将是数组分配。所以你仍然需要数组。

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

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

【讨论】:

  • 伟大的扩展 - 从您的描述看来,与 ArraySegment 相比,Span 和 ReadOnlySpan 提供了功能的超集...我什么时候可以使用 ArraySegment跨度?因为似乎我永远不会。
  • Span 是一个只有栈的结构(相当新的和棘手的语言特性)。您不能将它保存在某个非堆栈类或结构的字段中。所以 Span 非常强大,但在使用上也非常有限。另一方面,当您需要“缓冲区/偏移量/计数”三位一体并且无法使用 Span 时,ArraySegment 非常简单且有用。
  • 酷,这就像 golang 中的 Slice
【解决方案2】:

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

此外,虽然 ArraySegment 实现了 IEnumerable,但 Span 没有。

【讨论】:

    【解决方案3】:

    在决定是否使用 Span 时还要考虑在 C# 中应用于 ref structs 的限制:

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

    Span 是一个 ref struct 分配在堆栈上而不是 在托管堆上。 Ref 结构类型有许多限制 确保它们不能被提升到托管堆,包括 它们不能被装箱,不能分配给类型的变量 对象、动态或任何接口类型,它们不能是 引用类型,它们不能用于 await 和 yield 边界。此外,调用两个方法,Equals(Object) 和 GetHashCode,抛出 NotSupportedException。

    重要

    因为是stack-only类型,所以Span不适合很多 需要在堆上存储对缓冲区的引用的场景。这 例如,对于进行异步方法调用的例程来说是正确的。 对于这种情况,您可以使用免费的 System.Memory 和 System.ReadOnlyMemory 类型。

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

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

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

    将 ref 结构类型保持为堆栈分配变量的目标 介绍了编译器对所有 ref struct 强制执行的几条规则 类型。

    • 您不能将 ref 结构装箱。
    • 您不能将 ref struct 类型分配给对象、动态或任何接口类型的变量。
    • ref struct 类型不能实现接口。
    • 您不能将 ref 结构声明为类或普通结构的成员
    • 不能在异步方法中声明属于 ref struct 类型的局部变量。您可以在返回 Task、Task 或 Task-like 类型的同步方法中声明它们。
    • 不能在迭代器中声明 ref struct 局部变量。
    • 您无法在 lambda 表达式或局部函数中捕获 ref struct 变量。
    • 这些限制可确保您不会以可能将其提升到托管堆的方式意外使用 ref 结构。

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

    【讨论】:

      猜你喜欢
      • 2018-07-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-01-14
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多