How is the new C# Span<T> different from ArraySegment<T>?

Span<T> does not replace anything. It's value added. It provides a type-safe view into continuous segments of memory which can be allocated in many different ways: either as a managed array, a stack-based memory or unmanaged memory.

ArraySegment<T> is limited to managed arrays. You can't use it to wrap data allocated on the stack using stackalloc. Span<T> allows you to do that.

ArraySegment<T> also does not provide a read-only view into the underlying array. ReadOnlySpan<T> gives you that.

Span<T> is not supposed to replace arrays. At the end of the day it's just a view into data. That data has to be allocated somehow, and in managed world that allocation, for most cases, will be an array allocation. So you still need arrays.

You should use Span<T> if you want your code to be able to manipulate more than just arrays. E.g. consider a parsing library. Right now, to allow it to work with arrays, stack allocated memory and unmanaged memory it has to provide multiple entry points in the API for each of these, and use unsafe code to actually manipulate the data. It also probably would need to expose a string-based API to be used by people who have their data allocated as strings. With Span and ReadOnlySpan you can merge all that logic to a single, Span-based solution which will be applicable in all these scenarios.

Span<T> is definitely not going to be something that's used by everybody and very often. It's a highly specialized part of .NET framework useful mostly to library authors and in very high performance critical scenarios. E.g. Kestrel, the web service behind ASP.NET Core will get a lot of performance benefits from moving to Span<T> because e.g. parsing the request can be done using Span<T> and stack-allocated memory, which puts no pressure on GC. But you, writing websites and services based on ASP.NET Core don't necessary have to use it.


From MSDN Magazine: Span is defined in such a way that operations can be as efficient as on arrays: indexing into a span doesn’t require computation to determine the beginning from a pointer and its starting offset, as the ref field itself already encapsulates both. (By contrast, ArraySegment has a separate offset field, making it more expensive both to index into and to pass around.)

Also, while ArraySegment implements IEnumerable, Span doesn't.


Take also into consideration while deciding whether to use Span the limitations that applied to ref structs in C#:

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

Span is a ref struct that is allocated on the stack rather than on the managed heap. Ref struct types have a number of restrictions to ensure that they cannot be promoted to the managed heap, including that they can't be boxed, they can't be assigned to variables of type Object, dynamic or to any interface type, they can't be fields in a reference type, and they can't be used across await and yield boundaries. In addition, calls to two methods, Equals(Object) and GetHashCode, throw a NotSupportedException.

Important

Because it is a stack-only type, Span is unsuitable for many scenarios that require storing references to buffers on the heap. This is true, for example, of routines that make asynchrous method calls. For such scenarios, you can use the complimentary System.Memory and System.ReadOnlyMemory types.

For spans that represent immutable or read-only structures, use System.ReadOnlySpan.

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

Adding the ref modifier to a struct declaration defines that instances of that type must be stack allocated. In other words, instances of these types can never be created on the heap as a member of another class. The primary motivation for this feature was Span and related structures.

The goal of keeping a ref struct type as a stack-allocated variable introduces several rules that the compiler enforces for all ref struct types.

  • You can't box a ref struct.
  • You cannot assign a ref struct type to a variable of type object, dynamic, or any interface type.
  • ref struct types cannot implement interfaces.
  • You can't declare a ref struct as a member of a class or a normal struct.
  • You cannot declare local variables that are ref struct types in async methods. You can declare them in synchronous methods that return Task, Task or Task-like types.
  • You cannot declare ref struct local variables in iterators.
  • You cannot capture ref struct variables in lambda expressions or local functions.
  • These restrictions ensure you don't accidentally use a ref struct in a manner that could promote it to the managed heap.

You can combine modifiers to declare a struct as readonly ref. A readonly ref struct combines the benefits and restrictions of ref struct and readonly struct declarations.

Tags:

C#

.Net Core