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);
}
C# 12 in a Nutshell
Buy from amazon.com Buy print or Kindle edition
Buy from ebooks.com Buy PDF edition
Buy from O'Reilly Read via O'Reilly subscription