top of page

What is Span<T> in C#. How it is different from Memory<T>.

Span<T> is a value types which is an allocation-free representation of memory from different sources. Span<T> allows developers to work with regions of contiguous memory in a more convenient fashion ensuring memory and type safety.


Span<T> is a struct and it will NOT cause heap allocation. Span<T> provides type-safe access to memory while maintaining the performance of arrays.


We can use Span with:

  • arrays

  • strings

  • stack allocation

  • native buffers

It is very useful because we can simply slice an existing chunk of memory and manage it, we don’t have to copy it and allocate new memory.


List of types that we can convert to Span<T>:

  • Arrays

  • Pointers

  • stackalloc

  • IntPtr

With ReadOnlySpan<T> we can convert all above and string:

  • Arrays

  • Pointers

  • stackalloc

  • IntPtr

  • string



Span<t> Implementation


Ref return

The first step in wrapping head around Span<T> implementation for those who don’t closely follow updates in C# language is learning about ref returns which were introduced in C# 7.0.


While most of the readers are familiar with passing method argument by reference, now C# allows returning a reference to a value instead of the value itself.


Let us examine how it works. We’ll create a simple wrapper around an array of prominent musicians which exhibits both traditional behavior and new ref return feature.


public class ArtistsStore
{
    private readonly string[] _artists = 
            new[] { "Amenra", "The Shadow Ring", "Hiroshi Yoshimura" };

    public string ReturnSingleArtist()
    {
        return _artists[1];
    }

    public ref string ReturnSingleArtistByRef()
    {
        return ref _artists[1];
    }

    public string AllAritsts => string.Join(", ", _artists);
}

Now let’s call those methods:

var store = new ArtistsStore();
var artist = store.ReturnSingleArtist();
artist = "Henry Cow";
var allArtists = store.AllAritsts; //Amenra, The Shadow Ring, Hiroshi Yoshimura

artist = store.ReturnSingleArtistByRef();
artist = "Frank Zappa";
allArtists = store.AllAritsts;     //Amenra, The Shadow Ring, Hiroshi Yoshimuraref var artistReference = ref store.ReturnSingleArtistByRef();
artistReference = "Valentyn Sylvestrov";
allArtists = store.AllAritsts;     //Amenra, Valentyn Sylvestrov, Hiroshi Yoshimura

Observe that while in the first and the second example, the original collection is unmodified, in the final example, we’ve managed to alter the second artist of the collection. As you’ll see later during the course of the article, this useful feature will help us operate arrays located on the stack in a reference-like fashion.


Ref structs

As we know, value types might be allocated on stack. Also, they do not necessarily depend on the context where the value is used. In order to make sure that the value is always allocated on stack, the concept of ref struct was introduced in C# 7.0. Span<T> is a ref struct so we are sure that is always allocated on stack.


Span<t> Implementation

Span<T> is a ref struct which contains a pointer to memory and length of the span similar to below.


public readonly ref struct Span<T>
{
  private readonly ref T _pointer;
  private readonly int _length;
  public ref T this[int index] => ref _pointer + index;
  ...
}

Note ref modifier near the pointer field. Such construct can’t be declared in a plain C# in .NET Core it is implemented via ByReference<T>.


So as you can see, indexing is implemented via ref return which allows reference-type-like behavior for stack-only struct.



How Span<T> is different from Memory<T>?


Memory<T>

Memory <T> is a type complementing Span<T> . As discussed in its design document, Span<T> is a stack-only type. The stack-only nature of Span<T> makes it unsuitable for many scenarios that require storing references to buffers (represented with Span<T> ) on the heap, e.g. for routines doing asynchronous calls

async Task DoSomethingAsync(Span<byte> buffer) 
{
    buffer[0] = 0;
    await Something(); 
    // Oops! The stack unwinds here, but the buffer below
    
    // cannot survive the continuation.
    buffer[0] = 1;
}

To address this problem, we will provide a set of complementary types, intended to be used as general purpose exchange types representing, just like Span <T>, a range of arbitrary memory, but unlike Span <T> these types will not be stack-only, at the cost of significant performance penalties for reading and writing to the memory.

async Task DoSomethingAsync(Memory<byte> buffer) 
{
    buffer.Span[0] = 0;
    await Something(); 
    // The stack unwinds here, but it's OK as Memory<T> is
                       
                       
    // just like any other type.
    buffer.Span[0] = 1;
}

In the sample above, the Memory <byte> is used to represent the buffer. It is a regular type and can be used in methods doing asynchronous calls. Its Span property returns Span<byte>, but the returned value does not get stored on the heap during asynchronous calls, but rather new values are produced from the Memory<T> value. In a sense, Memory<T> is a factory of Span<T>.


Limitations of Span<T>

Span<T> 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 (the process of converting a value type to the type object or to any interface type implemented by this value type), 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.


Because it is a stack-only type, Span<T> is unsuitable for many scenarios that require storing references to buffers on the heap. This is true, for example, of routines that make asynchronous method calls.



Resources: newbedev.com, codemag.com


The Tech Platform

0 comments

Recent Posts

See All

Comments


bottom of page