.NET is a managed platform, that means the memory access and management is safe and automatic. All types are fully managed by .NET, it allocates memory either on the execution stacks, or managed heaps.
In the event of interop or low-level development, you may want the access to the native objects and system memory, here is why the interop part comes, there are types that can marshal into the native world, invoke native APIs, convert managed/native types and define a native structure from the managed code.
Problem 1: Memory access patterns
In .NET world, there are three types of memory you may be interested:
- Managed heap memory, such as an array;
- Stack memory, such as objects created by
- Native memory, such as a native pointer reference.
Each type of memory access may need to use language features that are designed for it:
- To access heap memory, use the
fixed(pinned) pointer on supported types (like
string), or use other appropriate .NET types that have access to it, such as an array or a buffer;
- To access stack memory, use pointers with
- To access unmanaged system memory, use pointers with
You see, different access pattern needs different code, no single built-in type for all contiguous memory access.
Problem 2: Performance
In many applications, the most CPU consuming operations are string operations. If you run a profiler session against your application, you may find the fact that 95% of the CPU time is used to call string and related functions.
SubString may be the most frequently used string APIs, and they are also very heavy:
SubString()returns a new string object that is part of the original string, this is unnecessary if there is a way to slice and return a portion of the original string to save one copy.
IsNullOrWhiteSpace()takes a string object that needs a memory copy (because string is immutable.)
- Specifically, string concatenation is expensive, it takes
nstring objects, makes
n - 1temporary string objects, and return a final string object, the
n – 1copies can be eliminated if there is a way to get direct access to the return string memory and perform sequential writes.
System.Span<T> is a stack-only type (
ref struct) that wraps all memory access patterns, it is the type for universal contiguous memory access. You can think the implementation of the Span<T> contains a dummy reference and a length, accepting all 3 memory access types.
You can create a Span<T> using its constructor overloads or implicit operators from array, stackalloc’d pointers and unmanaged pointers.
Once you have a Span<T> object, you can set value with a specified index, or return a portion of the span:
You can then use the
Slice() method to write a high performance
The above code does not copy over strings, nor generate new strings, it returns a portion of the original string by calling the
Because Span<T> is a ref struct, all ref struct restrictions apply. i.e. you cannot use Span<T> in fields, properties, iterator and async methods.
System.Memory<T> is a wrapper of
System.Span<T>, make it accessible in iterator and async methods. Use the
Span property on the Memory<T> to access the underlying memory, this is extremely helpful in the asynchronous scenarios like File Streams and network communications (
The following code shows simple usage of this type.
The Framework Class Library/Core Framework (FCL/CoreFx) will add APIs based on the span-like types for Streams, strings and more in .NET Core 2.1.
ReadOnlySpan<T> and ReadOnlyMemory<T>
System.ReadOnlySpan<T> is the read-only version of the
System.Span<T> struct where the indexer returns a
readonly ref object instead of
ref object. You get read-only memory access when using
readonly ref struct.
This is useful for
string type, because string is immutable, it is treated as read-only span.
We can rewrite the above code to implement the
Trim() method using ReadOnlySpan<T>:
As you can see, Nothing is changed in the method body; I just changed the parameter type from
ReadOnlySpan<T>, and used the implicit operator to convert a
string literal to
System.ReadOnlyMemory<T> is the read-only version of
System.Memory<T> struct where the
Span property is a
ReadOnlySpan<T>. When using this type, you get read-only access to the memory and you can use it with an iterator method or async method.
System.MemoryExtensions class contains extension methods for different types that manipulates with span types, here is a list of commonly used extension methods, many of them are the equivalent implementations for existing APIs using the span types.
- AsSpan, AsMemory: Convert arrays into Span<T> or Memory<T> or their read-only counterparts.
- BinarySearch, IndexOf, LastIndexOf: Search elements and indexes.
- IsWhiteSpace, Trim, TrimStart, TrimEnd, ToUpper, ToUpperInvariant, ToLower, ToLowerInvariant:
Span<char>operations similar to
In some case, you probably want to have lower level access to the memory types and system buffers, and convert between spans and read-only spans. The
System.Runtime.InteropServices.MemoryMarshal static class provides such functionalities to allow you control these access scenarios. The following code shows to title case a string using the span types, this is high performant because there is no temporary string allocations.
Span<T> and Memory<T> enables a uniform way to access contiguous memory, regardless how the memory is allocated. It is very helpful for native development scenarios, as well as high performance scenarios. Especially, you will gain significant performance improvements while using span types to work with strings. It is a very nice feature innovated in C# 7.2.
NOTE: To use this feature, you will need to use Visual Studio 2017.5 and language version 7.2 or latest.