[API Proposal]: Interlocked.CompareExchange overload that can work with pointers
- 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
Assessment
This issue has not been assessed yet.