dotnet / dotnet/runtime

[API Proposal]: Interlocked.CompareExchange overload that can work with pointers

Open
#128,085 7 comments 0 reactions 0 assignees View on GitHub
api-suggestion area-System.Threading
Dominant language
C#
Stars
18.3k
Forks
5.6k
PR merge metrics
PR metrics pending

Description

### Background and motivation

I would like to do something like this:

```cs
struct MyStruct {
public int Value;
}
unsafe class MyClass {
MyStruct* _myStruct;

MyStruct* CreateOnce ()
{
var ptr = (MyStruct *) NativeMemory.AllocZeroed ((nuint) sizeof (MyStruct));
var oldPtr = Interlocked.CompareExchange (ref _myStruct, ptr, null);
if (oldPtr is not null {
NativeMemory.Free (ptr);
return oldPtr;
}
return ptr;
}
}
```

but it [doesn't compile](https://lab.razor.fyi/#fZHNbhQxDMdVDj3kyBNYPc1U1bzAiEM1fBV1oWIrkLhAmvFOrWacwU5WHdA-Em_Aw6H52O6yaPEljv3zX__E5vepMTcSGrFt4fT5r9OkxA0se43Ylmb_VlTBe3SRAmvxBhmF3H-Iq7ZN0d55PGBekm04aCSnxztFFWq8ZOt7pUPsmvj7Qelj4kgtFlVoO_IoS5Q1OdQj2BVHlNAdoW7vBW1N3ByrF7dWH7Q0xnmrCvP3mZ8GAECjjeRgHaiGhSXO8rE8NYeoAmvwWHwWinhNjNnZW_Q-FGd5OUIbszFGoyQXYdEvp2Sa79KdJwfEET5Zn7A0G5NY7Qph8rLoq_Gc8O30OXxt57Q0B51K0Eb8wA7hH6trK9BFgReQPRk5z-G9jbTGBbZB-uLS--C-oASsIcs4EccclH5gWO2m8vlpW9Hg65tRd9yED-4B63F5VvDVo7u33CBkgqud8YvByQVw8n5PjFaQzWKkwCGOwN4ThvjL72tBhKyLsqcyhGBMwrOzXWvzlM1At-0Oa3r3zOm3k8eTPw):

> The type 'MyStruct*' may not be used as a type argument

I wrote this helper method, which works:

```cs
internal unsafe static T* InterlockedCompareExchange (ref T* location1, T* value, T* comparand) where T: unmanaged
{
fixed (T** ptr = &location1) {
return (T *) Interlocked.CompareExchange (ref Unsafe.AsRef (ptr), (IntPtr) value, (IntPtr) comparand);
}
}
```

but ugh... and I don't really know if it's safe or not either.

### API Proposal

```csharp
namespace System.Collections.Generic;

public class Interlocked
{
public unsafe static T* CompareExchange (ref T* location1, T* value, T* comparand) where T: unmanaged;
}
```

### API Usage

```cs
struct MyStruct {
public int Value;
}
unsafe class MyClass {
MyStruct* _myStruct;

MyStruct* CreateOnce ()
{
var ptr = (MyStruct *) NativeMemory.AllocZeroed ((nuint) sizeof (MyStruct));
var oldPtr = Interlocked.CompareExchange (ref _myStruct, ptr, null);
if (oldPtr is not null {
NativeMemory.Free (ptr);
return oldPtr;
}
return ptr;
}
}
```

### Alternative Designs

Keep using either my scary-looking `InterlockedCompareExchange` solution, or use IntPtr (which is somewhat error prone too):

```cs
struct MyStruct {
public int Value;
}
unsafe class MyClass {
IntPtr _myStruct;

MyStruct* CreateOnce ()
{
var ptr = (IntPtr) NativeMemory.AllocZeroed ((nuint) sizeof (MyStruct));
var oldPtr = Interlocked.CompareExchange (ref _myStruct, ptr, IntPtr.Zero);
if (oldPtr is not null {
NativeMemory.Free (ptr);
return (MyStruct *) oldPtr;
}
return (MyStruct *) ptr;
}
}
```

### Risks

Not sure if the proposed overload might conflict with the existing generic overload.

There might be other Interlocked APIs that this would apply to as well, I haven't looked.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.