Change owners, step through generations, pinning and finalization, or compare memory under load. The model explains concepts; the SDK examples measure real runtime observations.
Open the Memory Lab ↗The memory contract
A static field can provide a long-lived path from runtime state to application data. A static dictionary therefore remains an owner of its entries until they are removed or the relevant runtime lifetime ends. The collector cannot decide that a cached preview is no longer useful merely because its request has finished. Cache retention may be intentional and beneficial, but a useful design must specify limits, eviction, invalidation, and which component owns those policies instead of assuming cached objects will disappear as ordinary temporaries.
Trace the actual program
The prepared cache receives two byte arrays under keys A and B, so Count prints 2. Removing A leaves one entry and prints 1. Clearing the dictionary removes its remaining entries and prints 0. These are deterministic cache facts. They do not claim that every reference to those arrays has vanished, that the dictionary's backing storage shrank, or that the browser returned memory to the operating system. The small bounded example demonstrates explicit removal without implementing an automatic eviction strategy or pretending to measure a heap.
How the mechanism works
A cache policy often needs a quantity bound, a freshness rule, or both. A maximum item count is not the same as a maximum byte budget when entries vary greatly in size. Replacement can release the cache's reference to an older value, but another owner may still hold it. For resources implementing IDisposable, eviction also needs a documented disposal or borrowing contract; removing a dictionary entry does not automatically call Dispose. Separate logical lookup identity, display labels, stored ownership, and invalidation triggers so each can be tested independently.
Boundaries and misconceptions
Changing the dictionary to a weak-reference map does not automatically solve a missing policy. Weak entries can leave bookkeeping alive while targets disappear unpredictably, and required data still needs a fallback. Conversely, clearing a useful cache after every operation can increase allocation churn and latency. A retention diagnosis should ask whether the retained data matches the cache's documented purpose and bounds. Do not call a static field a leak simply because it is long-lived, and do not assume static means free of all lifecycle responsibilities.
Practice: change one ownership path
First store a replacement array under A instead of adding a third key. Predict that Count remains two, because key replacement changes a value rather than membership. Hint: an item-count bound says nothing about the new value's size. Then add C and predict three before removal. Sketch a maximum-two-entry policy with an explicit eviction order, and test which key disappears. Keep the tiny policy deterministic instead of relying on incidental dictionary enumeration as an undocumented eviction algorithm.
Practice: test a second boundary
Next let a separate owner retain the B array before clearing the cache and read its Length afterward. Predict sixteen through that owner. Hint: Clear removes cache edges, not other owners' references. Extend the cache interface with an explicit invalidation operation for a document closing, and test it at that lifecycle boundary. If entries own disposable resources, specify whether invalidation disposes them or only ends a borrowed reference. That decision must remain consistent with every caller that may still use the value.
Review the evidence
A useful cache review records its expected lifetime, size policy, eviction behavior, and ownership transfer. For the baseline, the only measured values are two, one, and zero entries. When a profiler later shows a static retaining path, compare it with that policy rather than recommending a full collection first. The right correction may be a missing invalidation, a byte-budget limit, or an intentionally longer lifetime with an honest memory cost. Ownership and evidence should guide the choice.
- The baseline cache membership counts are two, one, and zero.
- Replacement can change retained bytes without changing the item count.
- Eviction and disposal are separate policies that need an explicit owner.
A working example
using System;
using System.Linq;
using System.Collections.Generic;
using System.Text;
using System.Buffers;
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;
PreviewCache.Put("A", new byte[8]);
PreviewCache.Put("B", new byte[16]);
Console.WriteLine(PreviewCache.Count);
PreviewCache.Remove("A");
Console.WriteLine(PreviewCache.Count);
PreviewCache.Clear();
Console.WriteLine(PreviewCache.Count);
static class PreviewCache
{
private static readonly Dictionary<string, byte[]> items = new(StringComparer.Ordinal);
public static int Count => items.Count;
public static void Put(string key, byte[] value) => items[key] = value;
public static void Remove(string key) => items.Remove(key);
public static void Clear() => items.Clear();
}
Expected output
2 1 0
The baseline cache membership counts are two, one, and zero.