Skip to content

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

@rolfbjarne

Description

@rolfbjarne

Background and motivation

I would like to do something like this:

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:

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

I wrote this helper method, which works:

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

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

API Proposal

namespace System.Collections.Generic;

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

API Usage

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

    MyStruct* CreateOnce ()
    {
        var ptr = (MyStruct *) NativeMemory.AllocZeroed ((nuint) sizeof (MyStruct));
        var oldPtr = Interlocked.CompareExchange<MyStruct> (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):

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions