Description
This API proposal is extracted from #94113 (comment) and supersedes/fixes:
- [API Proposal]: GCHandle.GetTargetUnsafe() with no handle validation #94113
- Add Dispose/IDisposable to GCHandle #54792
- Add Dispose/IDisposable to GCHandle #54792 (comment)
Overview
The GCHandle
type currently has a number of issues/inefficiencies, that are impossible to completely fix without introducing some (major) breaking changes, which could cause all sorts of problems for people having a dependency on the current behaviors. It would be beneficial to instead add a new handle type, which solves all of these problems from the start:
- Unnecessary validation check (null check) when getting/setting targets
- Pin flag removal every time the handle target is resolved
- Interlocked operation from
Free
- Not implementing
IDisposable
We have a reference implementation which can be a starting point for this.
cc. @jkotas @AaronRobinsonMSFT @tannergooding
API Proposal
namespace System.Runtime.InteropServices;
public struct GCHandle<T> : System.IDisposable
where T : class
{
public GCHandle(T? value, GCHandleType type = GCHandleType.Normal);
public readonly bool IsAllocated { get; }
public T? Target { readonly get; set; }
public static GCHandle<T> FromIntPtr(IntPtr value);
public static IntPtr ToIntPtr(GCHandle<T> value);
public void Dispose();
}
public struct PinnedGCHandle<T> : System.IDisposable
where T : class
{
public PinnedGCHandle(T? value);
public readonly bool IsAllocated { get; }
public T? Target { readonly get; set; }
public static PinnedGCHandle<T> FromIntPtr(IntPtr value);
public static IntPtr ToIntPtr(PinnedGCHandle<T> value);
public void Dispose();
}
public static class GCHandleExtensions
{
public static T* AddrOfArrayData<T>(this PinnedGCHandle<T[]> handle);
public static char* AddrOfStringData(this PinnedGCHandle<string> handle);
}
Additional caveats
- Calling
Target
on a handle that's not allocated will throwNullReferenceException
- Calling
AddrOf*
on a handle that's not allocated will throwNullReferenceException
- Calling
AddrOf*
when the target isnull
will return anull
pointer - Converting between the two handles via
FromIntPtr/ToIntPtr
if the handle type is mismatched is undefined behavior Dispose
is not thread safe (but still no-op after the first call, just not in a thread-safe manner)
API Usage
Same as GCHandle
, really:
// In a constructor (or wherever)
this.handle = new GCHandle<MyObject>(myObj);
// Later on
MyObject? target = this.handle.Target;
Alternative Designs
The alternative would be to change GCHandle
, but as mentioned, it seems impossible to fix all issues with back-compat.
Risks
Not really any risk, since this would be a brand new type.
Metadata
Metadata
Assignees
Labels
Type
Projects
Status