Purpose of GC.KeepAlive in C# Interop
In C#, GC.KeepAlive is used to prevent the garbage
collector from prematurely collecting an object while its unmanaged
resources or raw pointers are still in use by native code. Because the
.NET Just-In-Time (JIT) compiler aggressively optimizes variable
lifetimes, an object can be considered eligible for collection as soon
as it is no longer referenced in managed execution, even if a native
method invoked by that object is still actively running. Using
GC.KeepAlive ensures the managed wrapper object—and any
finalizer or disposal logic tied to it—remains alive until the unmanaged
operation finishes.
The Problem: Aggressive Garbage Collection
In managed environments, the garbage collector does not inspect the
internal state of unmanaged functions called via Platform Invoke
(P/Invoke). It only tracks references within managed code. If a managed
object extracts an internal resource—such as an IntPtr
handle—and passes that handle to an unmanaged function, the managed
object itself is often no longer referenced in the remainder of the
caller method.
As a result, the JIT compiler can mark the managed object as dead while the native call is still executing. If a garbage collection occurs on another thread during that native call, the managed object might be finalized. If its finalizer frees the unmanaged handle, the native call will suddenly attempt to access invalid memory, causing an access violation or data corruption.
How GC.KeepAlive Resolves Premature Collection
The GC.KeepAlive method acts as an artificial reference.
When called, it informs the JIT compiler that the specified object must
be considered "in use" from the beginning of the routine up to the exact
line where GC.KeepAlive is called.
public class NativeResourceWrapper
{
private IntPtr _handle;
public NativeResourceWrapper()
{
_handle = NativeMethods.AllocateResource();
}
public void ProcessData()
{
// Native method takes the raw handle
NativeMethods.DoWork(_handle);
// Prevents wrapper from being finalized while DoWork runs
GC.KeepAlive(this);
}
~NativeResourceWrapper()
{
NativeMethods.FreeResource(_handle);
}
}At runtime, GC.KeepAlive does not perform any
computation; it is essentially a no-op that prevents the JIT optimizer
from dropping the object reference earlier in the method.
Modern Alternative: SafeHandle
While GC.KeepAlive is the standard solution for manually
managed pointers, modern .NET development often uses
SafeHandle classes (such as SafeFileHandle).
The P/Invoke marshaler automatically increments and decrements the
reference count of a SafeHandle around unmanaged calls.
This guarantees that the underlying resource cannot be closed or
collected during native execution, eliminating the need to manually
invoke GC.KeepAlive in most standard interop scenarios.