Memory & GC
var resource = new OwnedToken();
using (resource) { Console.WriteLine($"working:{!resource.Disposed}"); }
resource.Dispose();
Console.WriteLine($"disposed:{resource.Disposed};releases:{resource.Releases}");
Console.WriteLine("finalizer-timing:not-observed");
sealed class OwnedToken : IDisposable
{
public bool Disposed; public int Releases;
public void Dispose() { if (Disposed) return; Disposed = true; Releases++; }
}
Reveal answer and reasoning
The portable resource starts undisposed. Inside using it prints working:True. Leaving the using scope invokes Dispose, which changes Disposed and increments Releases once. Calling Dispose again returns immediately because the object already completed its release boundary. The next line therefore reports disposed:True;releases:1.
The finalizer-timing:not-observed line is intentional: this type has no finalizer and no native resource. The observable claim is idempotent managed state transition, not forced collection. The local variable remains available to inspect its state after disposal, demonstrating that an object can remain reachable while its external-resource lease has ended. Treating reachability as permission to continue using a disposed resource is a separate correctness mistake.
In the interactive scenario, disable the request owner, mark and reclaim. The finalizable owner is queued, so its child remains available. Finish the collection, run the queued model finalizer, then collect again. No resurrection is modeled. The explicit finalizer button represents a lifecycle event whose timing the real runtime controls; it is not an application API promising immediate execution.
The desktop helper creates one unrooted finalizable object in a non-inlined method, requests a full collection and waits for this bounded experiment's pending finalizers. Its finalizer only increments a counter and keeps its payload valid until return. A second collection is requested afterward. This controlled program is not evidence that waiting for arbitrary production finalizers is safe: real finalizers can block or depend on other resources, and diagnostic forcing changes the process being observed.
What a strong answer covers
- Finalization can retain an otherwise unreachable object graph until a later collection.
- GC does not automatically call IDisposable.Dispose.
- Resurrection creates a new ownership path and complicates reasoning.
Common pitfall
Assuming the GC invokes Dispose, or treating finalizer completion as immediate memory reclamation.
Follow-up questions
- What new path does resurrection introduce?
- How would a blocked finalizer affect unrelated pending cleanup?
Self-review
Choose what to revisit. These marks are your own assessment and are separate from lesson completion.