$ dotnet run51heap · string csv[0]1[1]2[2],[3]3[4]4[5],[6]5csv.Split(',')new string[3]"12""34""5"1var csv = "12,34,5";2ReadOnlySpan<char> s = csv;3int sum = 0, i;4while ((i = s.IndexOf(',')) >= 0) {5 sum += int.Parse(s[..i]);6 s = s[(i + 1)..];7}8sum += int.Parse(s);9Console.WriteLine(sum);stack frames.refs.lenisum12
Heap: the string csv, one cell per charStack frame: the span (s.ref, s.len), i and sumViolet brackets: the span's window over the charsBack row: the console, the heap holding the string "12,34,5" as 7 char cells, and what csv.Split(',') would allocate. Front row: the program and its stack frame. The violet brackets are the span: a reference and a length over chars that already exist. Slicing moves the brackets, never the chars. The program prints 51 and makes no new heap objects; Split makes 4.

Copies cost

  1. var csv = "12,34,5" points csv at one string object on the heap: 7 chars in a row, 2 bytes each. We want the sum of its numbers, 12 + 34 + 5, without making any more objects.

A span: a view

The payoff

What happens

// step 1/9 · string
line 1   var csv = "12,34,5";
heap     one string, 7 chars: [0]1 [1]2 [2], [3]3 [4]4 [5], [6]5
         a char is one UTF-16 code unit: 2 bytes
goal     12 + 34 + 5 = 51

Inside .NET a span holds a managed reference to its first element and an int length. The figure draws the reference as the index of the char it points at. String.Split returns a new array of new strings. The figure counts only those result objects.