Skip to content

Remove Interlocked.{Compare}Exchange generic constraint #65184

@GSPP

Description

@GSPP

EDITED by @stephentoub on 7/9/2024 to update the proposal:

Remove the T : class constraint from the existing Interlocked.{Compare}Exchange generic methods. They will be usable with not only reference type Ts but also primitive and enum Ts. Unsupported Ts will result in a NotSupportedException.

namespace System.Threading;

public static class Interlocked
{
    public static T CompareExchange<T> (ref T location1, T value, T comparand)
-       where T : class

    public static T Exchange<T>(ref T location1, T value)
-       where T : class
}

(We could also consider removing / making internal the new public overloads added in .NET 9 for byte/sbyte/ushort/short and just have the generic version to handle those.)


In principle, Interlocked.CompareExchange can be made to work on enums. Enums are just integers in disguise.

Motivating for this request is a repeated need to update a "state" field in a class. The state is most cleanly tracked as an enum. Since this API does not exist yet, I use integers and constants. That's of course viable but it would be cleaner to be able to use enums.

A workaround is to use Unsafe. There is nothing wrong with that in principle. It could be "nicer", though.

There's a recent discussion about small integer types that is pertinent to this issue:

This is related because enums can have small integer types. If we don't allow small types, then the generic constraint : enum would not ensure runtime safety.

So maybe we can add enum overloads iff we add small integer types. The alternative would be to add it even without that and fail at runtime.

Design issues:

  1. What APIs should be augmented like this in addition to CompareExchange. I can tell from the existing discussion that the team only wants to add API scope if there is demand. In principle, all Interlocked APIs could be upgraded.
  2. Should there be an API to process any unmanaged type of at most 64 bits in size?
    a. This would mean that there could be a runtime error depending on T. If T : unmanaged, then the JIT can ensure that this validation is without runtime overhead because each struct type generates specialized code.
    b. Could this result in unaligned access?
  3. Taking this further: There could be a generic type constraint "interlocked". This would then even work with things like ImmutableArray. Most likely, this is not going to happen, but I'm mentioning this for completeness.

For visitors looking for a workaround:

static T InterlockedCompareExchange32<T>(ref T location, T value, T comparand) where T : unmanaged
{
    var result = Interlocked.CompareExchange(ref Unsafe.As<T, int>(ref location), Unsafe.As<T, int>(ref value), Unsafe.As<T, int>(ref comparand));
    return Unsafe.As<int, T>(ref result);
}

Metadata

Metadata

Assignees

Labels

api-approvedAPI was approved in API review, it can be implementedarea-System.ThreadingblockingMarks issues that we want to fast track in order to unblock other important work

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions