Spans, ranges, and indices
Slice arrays, strings, and stack memory by position β and do it without allocating a single copy.
^ indexes from the end, .. carves a range, and Span<T> gives you a typed window over existing memory that never copies.
Indices count from both ends
C# 8 added the Index type and the ^ operator so you can address an element relative to the end of a collection. ^1 is the last element, ^2 the second-to-last, and ^0 is the position just past the end (the length itself). A plain integer like 2 is still an index from the start, so ^ simply flips the reference point. The compiler turns arr[^1] into arr[arr.Length - 1], so there is no bounds magic β ^0 on a non-empty array throws, exactly as arr[arr.Length] would.
You can store an index in a variable: Index last = ^1; is a first-class value, not just syntax.
Ranges slice with ..
The Range type and the .. operator describe a half-open slice: a..b includes index a up to but not including b. Omit an endpoint and it defaults to the start or the end, so 2.. means "from 2 to the end", ..3 means "the first three", and .. means "the whole thing". Ranges compose with ^, so 1..^1 trims the first and last elements.
On arrays and strings, slicing with a range allocates a new copy (int[], string). That is convenient but not free β which is exactly the problem Span<T> solves.
Span<T> is a window, not a copy
A Span<T> is a ref struct holding a pointer and a length: a typed view over memory you already own β an array, a stackalloc buffer, or a slice of another span. Slicing a span with a range produces another span over the same backing store, so span[2..5] allocates nothing and writes through to the original. Because it is a ref struct, a span lives only on the stack: it cannot be boxed, stored in a field of a class, captured by a lambda, or used across an await.
For read-only data β most importantly string, which exposes AsSpan() returning a ReadOnlySpan<char> β use ReadOnlySpan<T>. Parsing helpers like int.Parse accept a ReadOnlySpan<char>, so you can split and parse a string with zero intermediate string allocations.
Try it 4 examples
Index from the end with ^
C#introqueue[^1] and queue[^2] count back from the end to print dave and carol, and storing ^1 in an Index last reuses that end-relative offset. ^0 resolves to queue.Length, which is one past the final element, so indexing it throws IndexOutOfRangeException β confirming ^ is plain arithmetic, not a "safe" wrap-around.