Chapter 23 - Span and Memory
Slicing
void Main()
{
var numbers = new int [1000];
for (int i = 0; i < numbers.Length; i++) numbers [i] = i;
Sum (numbers).Dump ("total - using implicit conversion");
Sum (numbers.AsSpan()).Dump ("total - using .AsSpan()");
Sum (numbers.AsSpan (250, 500)).Dump ("total - slicing");
Span<int> span = numbers;
Console.WriteLine (span [^1]); // Last element
Console.WriteLine (Sum (span [..10])); // First 10 elements
Console.WriteLine (Sum (span [100..])); // 100th element to end
Console.WriteLine (Sum (span [^5..])); // Last 5 elements
}
int Sum (ReadOnlySpan<int> numbers)
{
int total = 0;
foreach (int i in numbers) total += i;
return total;
}
CopyTo
{
Span<int> x = new[] { 1, 2, 3, 4 };
Span<int> y = new int [4];
x.CopyTo (y);
y.Dump ("Copy");
}
{
Span<int> x = new[] { 1, 2, 3, 4 };
Span<int> y = new[] { 10, 20, 30, 40 };
x [..2].CopyTo (y [2..]); // y is now { 10, 20, 1, 2 }
y.Dump ("Copy - with span");
}
Working with strings
void Main()
{
CountWhitespace ("Word1 Word2").Dump();
CountWhitespace ("1 2 3 4 5".AsSpan (3,3)).Dump();
var span = "This ".AsSpan(); // ReadOnlySpan<char>
Console.WriteLine (span.StartsWith ("This")); // True
Console.WriteLine (span.Trim().Length); // 4
}
int CountWhitespace (ReadOnlySpan<char> s)
{
int count = 0;
foreach (char c in s)
if (char.IsWhiteSpace (c))
count++;
return count;
}
Memory of T
void Main()
{
Split ("Word1 Word2 Word3".AsMemory()).Dump();
}
IEnumerable<ReadOnlyMemory<char>> Split (ReadOnlyMemory<char> input)
{
int wordStart = 0;
for (int i = 0; i <= input.Length; i++)
if (i == input.Length || char.IsWhiteSpace (input.Span [i]))
{
yield return input [wordStart..i]; // Slice with C# range operator
wordStart = i + 1;
}
}
Splitting into Ranges
void Main()
{
ReadOnlySpan<char> source = "The quick brown fox";
foreach (var range in Split (source))
{
ReadOnlySpan<char> wordSpan = source [range];
wordSpan.Dump();
}
}
Range[] Split (ReadOnlySpan<char> input)
{
int pos = 0;
var list = new List<Range>();
for (int i = 0; i <= input.Length; i++)
if (i == input.Length || char.IsWhiteSpace (input [i]))
{
list.Add (new Range (pos, i));
pos = i + 1;
}
return list.ToArray();
}
CharSpanSplitter
void Main()
{
ReadOnlySpan<char> source = "The quick brown fox";
foreach (var word in source.Split())
word.ToString().Dump();
}
public readonly ref struct CharSpanSplitter
{
readonly ReadOnlySpan<char> _input;
public CharSpanSplitter (ReadOnlySpan<char> input) => _input = input;
public Enumerator GetEnumerator() => new Enumerator (_input);
public ref struct Enumerator // Forward-only enumerator
{
readonly ReadOnlySpan<char> _input;
int _wordPos;
public ReadOnlySpan<char> Current { get; private set; }
public Enumerator (ReadOnlySpan<char> input)
{
_input = input;
_wordPos = 0;
Current = default;
}
public bool MoveNext()
{
for (int i = _wordPos; i <= _input.Length; i++)
if (i == _input.Length || char.IsWhiteSpace (_input [i]))
{
Current = _input [_wordPos..i];
_wordPos = i + 1;
return true;
}
return false;
}
}
}
public static class CharSpanExtensions
{
public static CharSpanSplitter Split (this ReadOnlySpan<char> input)
=> new CharSpanSplitter (input);
}
Summing with pointers
unsafe void Main()
{
int* numbers = stackalloc int [1000]; // Allocate array on the stack
for (int i = 0; i < 1000; i++)
numbers [i] = i;
int total = Sum (numbers, 1000).Dump();
}
unsafe int Sum (int* numbers, int length)
{
int total = 0;
for (int i = 0; i < length; i++) total += numbers [i];
return total;
}
Span and stackalloc
unsafe void Main()
{
Span<int> numbers = stackalloc int [1000];
for (int i = 0; i < numbers.Length; i++)
numbers[i] = i;
int total = Sum (numbers).Dump();
}
int Sum (ReadOnlySpan<int> numbers)
{
int total = 0;
foreach (int i in numbers) total += numbers [i];
return total;
}
Span and unmanaged memory
unsafe void Main()
{
var source = "The quick brown fox".AsSpan();
var ptr = Marshal.AllocHGlobal (source.Length * sizeof (char));
try
{
var unmanaged = new Span<char> ((char*)ptr, source.Length);
source.CopyTo (unmanaged);
foreach (var word in unmanaged.Split())
Console.WriteLine (word.ToString());
}
finally { Marshal.FreeHGlobal (ptr); }
}
public readonly ref struct CharSpanSplitter
{
readonly ReadOnlySpan<char> _input;
public CharSpanSplitter (ReadOnlySpan<char> input) => _input = input;
public Rator GetEnumerator() => new Rator (_input);
public ref struct Rator // Forward-only enumerator
{
readonly ReadOnlySpan<char> _input;
int _wordPos;
public ReadOnlySpan<char> Current { get; private set; }
public Rator (ReadOnlySpan<char> input)
{
_input = input;
_wordPos = 0;
Current = default;
}
public bool MoveNext()
{
for (int i = _wordPos; i <= _input.Length; i++)
if (i == _input.Length || char.IsWhiteSpace (_input [i]))
{
Current = _input [_wordPos..i];
_wordPos = i + 1;
return true;
}
return false;
}
}
}
public static class CharSpanExtensions
{
public static CharSpanSplitter Split (this ReadOnlySpan<char> input)
=> new CharSpanSplitter (input);
public static CharSpanSplitter Split (this Span<char> input)
=> new CharSpanSplitter (input);
}