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
Pinning asks a compatible runtime not to move an object while a pin is active, typically because native interoperation needs a stable address. A pinned GCHandle also owns a retaining relationship that must be released. This example uses the managed handle API but never obtains an address, calls an operating-system function, or demonstrates native access. Its purpose is a bounded lifetime. CoreCLR's separate pinned-object heap is another architectural feature; creating a pinned handle for an existing array does not transfer that array into POH.
Trace the actual program
The three-byte array contains one, two, and three. GCHandle.Alloc creates a pinned handle, so the first IsAllocated observation prints True. The program adds the prepared byte values and prints 6. Finally frees the handle even if the protected body leaves through an ordinary managed exception. The final IsAllocated observation prints False. These are handle-state and arithmetic facts. They do not measure whether a collection moved anything, prove a physical heap region, or imply that the browser exposes native pointers for general application use.
How the mechanism works
A movable collector must account for active pins when arranging live objects, which can complicate compaction and contribute to fragmentation under certain workloads. Short, deliberate pin lifetimes are easier to reason about than indefinitely pinned young objects. CoreCLR's POH is used for designated pinned allocations supported by relevant APIs; it is not a destination to which every later pinning operation relocates an object. Runtime behavior and interop support vary, so consult the target's contract before converting an ownership experiment into production pinning advice.
Boundaries and misconceptions
GCHandle is a struct representing handle ownership, not an automatically reference-counted disposable class. Copying it casually can give two variables the same underlying handle token without giving them two independently releasable resources. Free exactly once through the responsible owner and stop using the handle afterward. A pin does not make the array's contents immutable, and it does not synchronize concurrent access. This managed example remains bounded and does not claim that arbitrary native interop is supported by SharpRune's execution profile.
Practice: change one ownership path
First change the array values to two, three, and four. Predict True, nine, and False without changing the handle lifetime. Hint: the sum is ordinary data processing; the allocated-state lines come from acquiring and freeing the handle. Then deliberately throw a controlled InvalidOperationException inside the try body and catch it outside that lifetime. Verify that finally still frees the handle. Keep the error experiment separate from the original exact-output expectation.
Practice: test a second boundary
Next replace the handle creation with an unpinned normal handle in an independent experiment, while retaining the same IsAllocated checks. Explain why those booleans alone cannot prove pinning or heap location. Hint: they report ownership of a handle, not its physical effects. Compare the two requested handle kinds in the documentation. Finally describe when a short-lived interop owner would end the pin and why pooling, long-lived pins, and collector-specific POH APIs require an actual workload and target before deciding they improve performance.
Review the evidence
The review should identify the owner of the pin, the protected scope, the release path, and the limits of the observation. For the unchanged sample, only True, six, and False are deterministic. The broader collector discussion is explicitly qualified by implementation. Keep object liveness, movement restrictions, payload mutation, native-address access, and POH placement as separate concerns; conflating them leads to subtle lifetime errors even when a tiny program seems to work. Never copy a GCHandle token and then free both copies as though each owned a separate pin; the underlying ownership is shared.
- The original outputs are True, six, and False.
- Free ends the owned handle lifetime; it is not a POH migration.
- A pin prevents movement where supported, not mutation or unsynchronized access.
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;
byte[] values = { 1, 2, 3 };
var handle = GCHandle.Alloc(values, GCHandleType.Pinned);
try
{
Console.WriteLine(handle.IsAllocated);
Console.WriteLine(values.Sum(value => (int)value));
}
finally { handle.Free(); }
Console.WriteLine(handle.IsAllocated);
Expected output
True 6 False
The original outputs are True, six, and False.