Middle · Trace · Span, Memory and hidden backing-store retention

Trace the portable program: what is guaranteed, and what does it not measure?

Think through your answer before opening the explanation.

Memory & GC

using System.Runtime.InteropServices;
var source = new byte[1_048_576]; source[0] = 7;
ReadOnlyMemory<byte> view = source.AsMemory(0, 12);
MemoryMarshal.TryGetArray(view, out ArraySegment<byte> segment);
var copy = view.ToArray();
Console.WriteLine($"view:{view.Length};backing:{segment.Array!.Length};copy:{copy.Length}");
source[0] = 9;
Console.WriteLine($"view-first:{view.Span[0]};copy-first:{copy[0]}");
Reveal answer and reasoning

The portable program creates a one-megabyte byte array and a twelve-byte ReadOnlyMemory view. MemoryMarshal.TryGetArray extracts the backing array segment because this particular view is array-backed. The first output reports logical view length twelve, backing length 1,048,576 and copy length twelve. Those are real managed identities and lengths, not estimated heap sizes.

Changing source[0] afterward changes what view.Span[0] reads, while the independent copy retains the earlier value. The second line therefore reports view-first:9;copy-first:7. The extracted ArraySegment itself holds an array reference. If you kept it while testing whether copying released the original backing store, the diagnostic variable would create another owner and invalidate that inference. The desktop experiment explicitly clears that segment before changing the cached value.

In the interactive slice scenario, the cache and the request can both retain the same view. Disable the cache entry while leaving the request root enabled and inspect the backing array's retaining path. Then end the request. The model depicts a box for the view to make the graph legible; it is not a claim that every Memory struct is a separate object with that exact shallow size in a real process.

The SDK program proves that an array-backed twelve-byte view retains a one-megabyte backing array and that ToArray creates a twelve-byte backing store for the copied result. It deliberately does not claim immediate collection or RSS shrinkage. MemoryMarshal.TryGetArray is not a universal operation for every memory implementation: custom memory managers can have different backing storage, so production code must respect its success result and abstraction boundary.

What a strong answer covers

  • A slice can retain the whole backing allocation.
  • Memory<T> is not an ownership or disposal token.
  • Async consumers must finish before an owned buffer is returned.

Common pitfall

Treating a short ReadOnlyMemory slice as a small independent immutable allocation.

Follow-up questions

  1. How can a diagnostic ArraySegment accidentally keep the old backing store alive?
  2. When is a small copy worth its allocation cost?

Self-review

Choose what to revisit. These marks are your own assessment and are separate from lesson completion.

Primary references

Open lesson and code ↗

← All interview questions