Memory & GC
var cache = new Cache();
cache.Entry = new Payload("first");
var previous = cache.Entry;
cache.Entry = new Payload("second");
Console.WriteLine($"current:{cache.Entry.Name}");
Console.WriteLine($"previous:{previous.Name}");
cache.Entry = null;
Console.WriteLine($"cache-empty:{cache.Entry is null}");
GC.KeepAlive(previous);
sealed class Cache { public Payload? Entry; }
sealed record Payload(string Name);
Reveal answer and reasoning
The portable program gives the first payload two possible owners over its lifetime. Initially cache.Entry refers to first. Copying that reference into previous creates another strong path to the same instance. Replacing cache.Entry with second changes only the cache field; previous still refers to first. The first two output lines therefore identify second and first respectively. Setting cache.Entry to null makes the field empty, producing cache-empty:True, while GC.KeepAlive explicitly requires previous to remain live through its call.
Nothing in those three lines measures collection, promotion, object addresses or freed bytes. The program executes real reference assignments, but it does not introspect the collector. If you remove KeepAlive, source-level scope alone still does not establish exactly when the runtime stops reporting the local as live. Explain the result in terms of observable ownership and use a diagnostic experiment for generation claims.
Run the portable program first and predict all three outputs. In the interactive model, separately remove the old-to-young edge while leaving the old root enabled. A Gen 0 collection can now reclaim the young entry because that path is absent; the old cache remains outside the reclamation set. Undo the change and inspect the retaining path. This separates a reference-write question from a root-removal question.
For the SDK experiment, save the effective runtime, GC mode and generation output. Repeat in a clean process. If a profiler reports unexpected promotion, inspect surviving types and roots under the actual workload. Do not explain a service-wide memory rise using only one object's generation. That object may be representative, incidental, or held alive by the diagnostic code that was added to inspect it.
What a strong answer covers
- A minor collection does not reclaim the whole heap.
- Old-to-young reference writes must remain visible to a young collection.
- Removing one edge does not remove another strong owner.
Common pitfall
Treating an older object outside a minor collection as if it had already been reclaimed, or treating GC bookkeeping as application synchronization.
Follow-up questions
- What changes when the old cache loses its root before a Gen0 collection?
- Does the write barrier also make a compound cache update thread-safe?
Self-review
Choose what to revisit. These marks are your own assessment and are separate from lesson completion.