Copies cost
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.
The easy way, csv.Split(','), copies. It makes a new array and 3 new strings, "12", "34", "5": 4 new heap objects. In a loop that runs a million times, that is work for the garbage collector.
A span: a view
ReadOnlySpan<char> s = csv; copies nothing. A span is just two values on the stack: a reference to the first char and a length, 7. It is a window onto memory that already exists.
s.IndexOf(',') searches only the chars inside the window and returns 2: the comma is at position 2 of the span. The loop runs while a comma is found.
s[..i] is a smaller view: the first 2 chars, "12". int.Parse reads those digits straight from the original string, so sum becomes 12. No new string "12" is ever made.
s = s[(i + 1)..] moves the window past the comma. Only the span's two numbers change: it now starts at char 3 with length 4. The string itself is untouched.
The loop repeats on the smaller window. IndexOf finds the comma at 2, int.Parse reads "34" in place, sum becomes 46, and the window slides to the last char, "5".
The payoff
Now IndexOf returns -1, so the loop ends. Line 8 parses the last view, "5", and line 9 prints 51. Split made 4 heap objects; the span parsing made none.
Span<T> is a ref struct, so the compiler keeps it on the stack: it cannot be boxed (copied into a heap object), be a field of a class, or be kept across an await. So a span never outlives the memory it views.
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.