Memory & Garbage Collection · Lesson 11

Weak references are optional access

Use TryGetTarget without treating a weak reference as a guaranteed cache or cleanup signal.

Explore the interactive Memory Lab

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 weak reference does not by itself keep its target alive. It can provide optional access while a target remains available, but callers must be prepared for absence. TryGetTarget combines the test and acquisition of a usable strong reference in one operation. Weak references are not a general cure for every cache or event-retention problem: they add indeterminate availability, and the surrounding design still needs a miss policy, ownership rules, and a reason to retain the weak-reference objects themselves.

Trace the actual program

The sample first keeps Payload strongly reachable through the explicit KeepAlive boundary. The weak lookup therefore succeeds and prints True, followed by the name rune. A separate weak reference is intentionally constructed without a target, so its lookup prints False. That final result is not evidence that the collector ran; the reference began empty. No collection timing is forced and no finalizer prints a message. The obtained found variable is itself a strong reference while the program uses it, which is another reason to reason about the full graph.

How the mechanism works

A successful TryGetTarget gives the caller a target it can use through that acquired reference. A failed lookup means the caller must follow its stated fallback, often recreating a cheap optional value or reporting absence. Do not test a target in one operation and assume a separate later weak lookup still succeeds. Also avoid treating a weak reference as ownership of a scarce external resource. Whether the target is useful, disposable, or expensive to reconstruct remains part of the application's resource and cache contracts.

Boundaries and misconceptions

Setting one strong local to null does not guarantee that the next weak lookup fails. Other references can remain, runtime liveness can differ, and a collection may not occur at that moment. Forcing collections to make a weak-reference test turn false changes the workload and can still teach the wrong lifetime model. The deterministic empty reference in this sample is labeled honestly as empty, not as a fake simulation of collection. A real retention investigation uses retaining paths and collector evidence from the target runtime.

Practice: change one ownership path

First change the payload's name to archive and predict that only the successful name line changes. Hint: the strongly owned target remains available, while the separate empty reference remains empty. Then remove the empty-reference section and perform two successful weak lookups while explicitly keeping strong alive. Explain why both can be used safely here without generalizing that a weak cache always retains data. The KeepAlive contract, not a promise attached to WeakReference, establishes this experiment's required liveness.

Practice: test a second boundary

Next build a small helper that accepts a WeakReference and returns either the acquired Name or an explicit miss label. Test it with the live target and with the intentionally empty reference. Hint: use the out value from the same successful TryGetTarget call. Finally design a cache miss policy that recreates optional data and discuss whether a bounded ordinary cache would be simpler. Do not let unpredictable target availability silently change required application state or replace deterministic resource cleanup.

Review the evidence

The exact baseline True, rune, and False combines one deliberately strong owner with one deliberately empty weak target. It does not measure a collection. A complete explanation distinguishes the weak handle, its optional target, the strong reference obtained on success, and every other owner in the graph. Use weak references when optional availability matches the requirement, and document the fallback. They should clarify a lifecycle policy rather than hide an ownership problem behind a hope that the collector chooses a convenient moment.

  • The live target is kept strongly reachable through the observation.
  • The final false result comes from an intentionally empty weak reference.
  • A successful out target is the reference the caller should use.

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;

var strong = new Payload("rune");
var weak = new WeakReference<Payload>(strong);
Console.WriteLine(weak.TryGetTarget(out var found));
Console.WriteLine(found?.Name ?? "missing");
var empty = new WeakReference<Payload>(null!);
Console.WriteLine(empty.TryGetTarget(out _));
GC.KeepAlive(strong);
record Payload(string Name);

Expected output

True
rune
False
Try this
The live target is kept strongly reachable through the observation.

Further reading