Arrays, Span<T> & Memory<T>

11 questions found

How do you declare and initialize a fixed-size array in C#?

Beginner
Arrays in C# have a fixed size set at creation time, declared with square brackets after the element type, and can be initialized with a collection expression or explicit 'new' syntax.
int[] numbers = new int[5];
int[] scores = { 90, 85, 78 };
string[] names = new string[] { "Sam", "Alex" };
Real-world example Storing a fixed set of monthly sales figures where the count (12 months) never changes.

Common follow-ups: What's the default value of each element in a newly created int[] array?

Collections

How do you create and work with a multidimensional (rectangular) array versus a jagged array?

Beginner
A rectangular array (int[,]) has a fixed grid shape where every row has the same length; a jagged array (int[][]) is an array of arrays, where each inner array can have a different length, offering more flexibility at the cost of an extra indirection.
int[,] grid = new int[3, 3]; // rectangular: fixed 3x3
grid[0, 0] = 1;

int[][] jagged = new int[3][];
jagged[0] = new int[] { 1, 2 };
jagged[1] = new int[] { 1, 2, 3 };
Real-world example Using a rectangular array for a fixed game board, versus a jagged array for storing rows of variable-length CSV data.

Common follow-ups: Which performs better in tight loops — rectangular or jagged arrays?

Fundamentals

What is Span<T> and what core performance problem does it solve?

Beginner
Span<T> is a stack-only (ref struct) type representing a contiguous, type-safe view over a region of memory — it lets you slice arrays, strings, or stack-allocated memory WITHOUT copying data, avoiding the allocation overhead of creating new sub-arrays or substrings for temporary operations.
int[] source = { 1, 2, 3, 4, 5 };
Span<int> slice = source.AsSpan(1, 3); // view over elements [2, 3, 4], no copy
slice[0] = 99; // mutates the original array too, since it's a view
Real-world example Parsing a large string or byte buffer in-place (e.g., a CSV line) without allocating a new substring for every field.

Common follow-ups: Why can't Span<T> be stored as a field in a regular (non-ref struct) class?

Memory & Garbage Collection

How does Memory<T> differ from Span<T>, and when would you use Memory<T> instead?

Intermediate
Memory<T> is a heap-allocatable counterpart to Span<T> — since Span<T> is a ref struct restricted to the stack (can't be used in async methods, lambdas capturing it, or as a class field), Memory<T> can be stored anywhere and converted to a Span<T> (via .Span) only when you actually need to access the underlying data synchronously.
async Task ProcessAsync(Memory<byte> buffer) {
  await Task.Delay(10); // Span<T> couldn't survive this await, but Memory<T> can
  Span<byte> span = buffer.Span; // convert to Span<T> only when actually processing
  span[0] = 0xFF;
}
Real-world example Passing a buffer through an async I/O pipeline (like a network stream reader) where Span<T> couldn't cross an 'await' boundary.

Common follow-ups: Why exactly can't a Span<T> be used across an 'await' in an async method?

Asynchronous Programming

How do you slice an array or string efficiently without allocating a new array/string, using Span<T> or ReadOnlySpan<T>?

Intermediate
Call .AsSpan() (with optional start/length arguments) on an array or string to get a Span<T> or ReadOnlySpan<char> view over just that portion — no new memory is allocated, unlike Substring() or array slicing with LINQ's Skip/Take, which both copy data.
string text = "Hello, World!";
ReadOnlySpan<char> hello = text.AsSpan(0, 5); // 'Hello', no allocation

int[] arr = { 1, 2, 3, 4, 5 };
Span<int> middle = arr.AsSpan(1, 3); // [2, 3, 4], no allocation
Real-world example Parsing a large log file line-by-line, slicing out fields without allocating a new string for every single token.

Common follow-ups: How much of a real-world performance difference does this make compared to Substring() in a hot parsing loop?

String Handling & StringBuilder

What restrictions does the C# compiler enforce on Span<T> because it's a 'ref struct'?

Intermediate
Because Span<T> can point directly at stack memory, the compiler restricts it to prevent that memory from outliving its valid scope: it cannot be boxed, cannot be a field of a non-ref-struct class, cannot be used in async methods or iterators (yield return), and cannot be captured by a lambda or local function that might outlive the current stack frame.
// class Container { Span<int> data; } // Error: cannot use Span<T> as a field in a regular class

async Task Bad() {
  Span<int> span = stackalloc int[10];
  await Task.Delay(1); // Error: cannot use Span<T> across an await
}
Real-world example Understanding why a Span<T>-based helper method can't be trivially converted into an async method or a yield-based iterator.

Common follow-ups: What's the underlying CLR reason these specific restrictions exist for ref structs?

Iterators & yield return

How does 'stackalloc' work together with Span<T> to allocate memory on the stack instead of the heap?

Advanced
'stackalloc' allocates a block of memory directly on the CURRENT STACK FRAME (not the garbage-collected heap), and assigning it to a Span<T> gives you safe, bounds-checked access to that memory — ideal for small, short-lived buffers in performance-critical code, completely avoiding GC pressure, but the memory is only valid for the lifetime of the current method call.
Span<int> buffer = stackalloc int[100]; // allocated on the stack, zero GC pressure
for (int i = 0; i < buffer.Length; i++) {
  buffer[i] = i * i;
}
Real-world example Building a small, fixed-size scratch buffer for a hot-path numeric algorithm, avoiding heap allocation entirely.

Common follow-ups: What happens if you try to stackalloc a very large amount of memory, like 1MB?

Memory & Garbage Collection

How would you write a high-performance CSV field parser using ReadOnlySpan<char> that avoids all intermediate string allocations?

Advanced
Use ReadOnlySpan<char>.IndexOf() or Split-style enumeration to locate delimiters directly within the original string's span, slicing out each field as its own ReadOnlySpan<char> — only convert a field to an actual 'string' (via .ToString()) at the very last moment when you genuinely need to store or return it as a heap-allocated string.
ReadOnlySpan<char> line = "Sam,30,Engineer".AsSpan();
int firstComma = line.IndexOf(',');
ReadOnlySpan<char> name = line.Slice(0, firstComma); // no allocation yet
ReadOnlySpan<char> rest = line.Slice(firstComma + 1);
// ... continue slicing 'rest' for remaining fields
Real-world example Parsing millions of lines of a large CSV or log file in a batch job where allocation overhead directly impacts throughput.

Common follow-ups: At what point in this pipeline would you actually be FORCED to allocate a real string?

String Handling & StringBuilder

How does the modern .NET Span-based string.Split (via MemoryExtensions) or manual span-splitting reduce garbage collection pressure compared to string.Split()?

Advanced
Regular string.Split() allocates a new string array AND a new string object for EVERY resulting substring; span-based splitting techniques (like a manual span-walking loop, or newer .NET SpanSplitEnumerator APIs) can iterate over each segment as a ReadOnlySpan<char> without allocating anything for segments you only need to read or compare, not store.
ReadOnlySpan<char> csv = "a,b,c,d,e".AsSpan();
foreach (var segment in csv.Split(',')) { // conceptual, using newer span-splitting APIs
  // process each segment as a span, zero string allocations
}
// vs. "a,b,c,d,e".Split(',') -- allocates a string[] AND 5 string objects
Real-world example Reducing GC pauses in a high-throughput data ingestion pipeline processing millions of delimited records per second.

Common follow-ups: Why does reducing allocations matter more for GC PAUSE TIME than for raw memory usage in a server application?

Memory & Garbage Collection

How would you use Span<T> to implement an in-place array reversal or sorting algorithm without allocating a temporary array?

Advanced
Span<T> supports the same indexed read/write access as a regular array with none of the allocation overhead of copying — algorithms like in-place reversal or a custom sort can operate directly on a Span<T> (or a slice of one), swapping elements by index exactly as you would on a raw array, but with the added flexibility of it being a VIEW that could point into a larger buffer.
void ReverseInPlace(Span<int> span) {
  int left = 0, right = span.Length - 1;
  while (left < right) {
    (span[left], span[right]) = (span[right], span[left]);
    left++; right--;
  }
}
int[] arr = { 1, 2, 3, 4, 5 };
ReverseInPlace(arr.AsSpan()); // arr is now { 5, 4, 3, 2, 1 }, zero extra allocation
Real-world example Implementing performance-critical, allocation-free array manipulation utilities in a numeric or data-processing library.

Common follow-ups: Could this same ReverseInPlace function also operate directly on memory allocated via stackalloc?

Iterators & yield return

Showing 1–10 of 11