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
SafeHandle is a .NET abstraction for owning an unmanaged handle lifetime, with infrastructure that is safer than casually duplicating raw-handle finalizer code. A real derived wrapper must release its actual platform resource correctly. This portable lesson creates no operating-system handle: its nonzero IntPtr is explicitly a demonstration token, and ReleaseHandle records only a managed counter. The example observes the SafeHandle lifecycle API, not native interoperation, file closure, or real resource reclamation. Do not copy its pretend release body into a production wrapper.
Trace the actual program
The demonstration token begins open, so IsClosed prints False. Explicit Dispose closes its owned lifecycle and invokes the test ReleaseHandle body. The next observation prints True. A second Dispose does not invoke that release body again, leaving the final ReleaseCalls value at 1. No native function is called and no finalizer timing is forced. These exact outputs verify the explicit protocol. They do not demonstrate whether a particular host runs fallback finalization at shutdown or prove that an arbitrary raw pointer is safe to use.
How the mechanism works
For real interoperation, an appropriate existing library SafeHandle often already knows the platform's invalid values and correct release operation. An owning managed type can then implement IDisposable by disposing that wrapper rather than duplicating low-level lifetime logic. SafeHandle's infrastructure includes protections around its supported use, but it does not validate arbitrary native API contracts for you. Ownership transfer, borrowing, concurrent use, invalid-handle sentinels, and error reporting must still agree with the actual platform wrapper. Those concerns are intentionally absent from this managed-only laboratory.
Boundaries and misconceptions
IntPtr is only an integer-sized address or token representation; constructing one does not allocate or validate a native resource. Returning true from a real ReleaseHandle without actually releasing its resource would be a defect, even though this test wrapper can deliberately use a counter as its entire stated contract. Also avoid using DangerousGetHandle as a shortcut around ownership guarantees. This lesson never needs a raw address and should not be presented as permission to call unsupported native libraries from the browser runtime.
Practice: change one ownership path
First call Dispose three times instead of two. Predict that the release count stays one while IsClosed remains true. Hint: the observed behavior belongs to the wrapper's explicit lifecycle, not to a new garbage collection. Then create two separate demonstration tokens and dispose both. Predict one release call per instance, two in total when their observations are combined. Distinguish two owners from two references to the same owner before interpreting the count.
Practice: test a second boundary
Next place one token inside a using scope and throw a controlled managed exception from the body, handling it outside. Verify that the owned token becomes closed. Hint: using supplies deterministic disposal on ordinary unwinding. Then write a design note for wrapping an actual handle, listing its real release API and ownership-transfer rule without implementing or calling it here. Explain why that additional platform contract cannot be inferred from the harmless integer token used by this demonstration.
Review the evidence
The goal is an accurate ownership principle: use a suitable SafeHandle for a genuine native handle, dispose it through the responsible owner, and avoid inventing custom finalizer timing guarantees. The sample's False, True, and one outputs are deliberately narrower evidence. A correct explanation states that its token is not an OS resource and that no native call occurred. This keeps the lesson portable while preventing an educational test double from masquerading as production resource-release code. A real wrapper also needs a correct invalid-handle rule and release-failure policy for the resource API it represents.
- The demonstration token does not represent an operating-system resource.
- Two Dispose calls on one wrapper produce one test release call.
- A production ReleaseHandle must perform the real resource-specific release correctly.
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 token = new DemonstrationHandle();
Console.WriteLine(token.IsClosed);
token.Dispose();
Console.WriteLine(token.IsClosed);
token.Dispose();
Console.WriteLine(token.ReleaseCalls);
sealed class DemonstrationHandle : SafeHandle
{
public int ReleaseCalls { get; private set; }
public DemonstrationHandle() : base(IntPtr.Zero, ownsHandle: true) { SetHandle(new IntPtr(1)); }
public override bool IsInvalid => handle == IntPtr.Zero;
protected override bool ReleaseHandle()
{
ReleaseCalls++;
handle = IntPtr.Zero;
return true;
}
}
Expected output
False True 1
The demonstration token does not represent an operating-system resource.