19

I'm trying to use Interlocked.CompareExchange with this enum:

public enum State {
    Idle,
    Running,
    //...
}

The following code doesn't compile, but that's what I want do do:

if (Interlocked.CompareExchange(ref state, State.Running, State.Idle) != State.Idle) {
    throw new InvalidOperationException("Unable to run - not idle");
}

Sure I can use a int instead of the enum and use a property:

private int state = (int)State.Idle;
public State { get { return (State)state; } }

Then cast the enums to a int:

if (Interlocked.CompareExchange(ref state, (int)State.Running, (int)State.Idle) !=  (int)State.Idle) {
    throw new InvalidOperationException("Unable to run - not idle");
}

But are there better ways to do this?

13
  • 6
    What you show (treating it as an int and casting) is basically exactly what I do. Commented Aug 21, 2013 at 13:24
  • @MarcGravell: basically?
    – joe
    Commented Aug 21, 2013 at 13:24
  • To be honest it's not really that big a deal. Personally I would just keep it as an enum but cast it before you do the exchange.
    – James
    Commented Aug 21, 2013 at 13:24
  • 3
    @James you can't do that; the field needs to be int to use it as ref in the call. You can't cast during a ref. Commented Aug 21, 2013 at 13:25
  • @James: That will kill the reason to use CompareExchange.
    – joe
    Commented Aug 21, 2013 at 13:26

6 Answers 6

20

It's possible from IL, and it's possible to create a helper method for this that can be used from C#.

using System;
using System.Reflection;
using System.Reflection.Emit;
using System.Threading;

static class CompareExchangeEnumImpl<T>
{
    public delegate T dImpl(ref T location, T value, T comparand);
    public static readonly dImpl Impl = CreateCompareExchangeImpl();

    static dImpl CreateCompareExchangeImpl()
    {
        var underlyingType = Enum.GetUnderlyingType(typeof(T));
        var dynamicMethod = new DynamicMethod(string.Empty, typeof(T), new[] { typeof(T).MakeByRefType(), typeof(T), typeof(T) });
        var ilGenerator = dynamicMethod.GetILGenerator();
        ilGenerator.Emit(OpCodes.Ldarg_0);
        ilGenerator.Emit(OpCodes.Ldarg_1);
        ilGenerator.Emit(OpCodes.Ldarg_2);
        ilGenerator.Emit(
            OpCodes.Call,
            typeof(Interlocked).GetMethod(
                "CompareExchange",
                BindingFlags.Static | BindingFlags.Public,
                null,
                new[] { underlyingType.MakeByRefType(), underlyingType, underlyingType },
                null));
        ilGenerator.Emit(OpCodes.Ret);
        return (dImpl)dynamicMethod.CreateDelegate(typeof(dImpl));
    }
}

public static class InterlockedEx
{
    public static T CompareExchangeEnum<T>(ref T location, T value, T comparand)
    {
        return CompareExchangeEnumImpl<T>.Impl(ref location, value, comparand);
    }
}

public enum Foo
{
    X,
    Y,
}

static class Program
{
    static void Main()
    {
        Foo x = Foo.X;
        Foo y = Foo.Y;
        y = InterlockedEx.CompareExchangeEnum(ref x, y, Foo.X);
        Console.WriteLine("x: " + x);
        Console.WriteLine("y: " + y);
    }
}

Output:

x: Y
y: X

This just forwards the arguments to the correct Interlocked.Exchange overload. It fails badly if T isn't really an enum type, or its underlying type doesn't have an Interlocked.Exchange overload.

The generated IL is verifiable, at least according to PEVerify, as can be checked by making this use AssemblyBuilder and saving the result to a file.

4
  • 2
    @DarthVader Why not? I haven't ever had a reason to use Interlocked.Exchange with enums, but I do have other cases where there's a clear and correct way to do something, CIL allows it, but C# doesn't. In that case, I don't think C# is the right tool for the job, so I don't use C#.
    – user743382
    Commented Aug 21, 2013 at 14:11
  • 1
    How naughty! But I love it and copied it straight into my code :) Now, can you also do a Thread.VolatileRead(myEnum)? Commented Apr 9, 2015 at 23:25
  • @EugeneBeresovsky Sure, I don't see why not. It should be easy to adapt my answer to handle that. There are a lot of other methods that could also make sense to add, I don't think it'll benefit SO to include all of them here in an answer. :)
    – user743382
    Commented Apr 10, 2015 at 21:37
  • @EvgeniyBerezovsky see modernized Net Core response below. Just as easy to drop in and outperforms the Net6 native implementation by 1000x
    – user3259797
    Commented Sep 10, 2022 at 0:23
12

To make it simple, no :-)

Sadly C#/.NET consider enums as full type, partially disconnected from their base type. Every time you try to do something "fancy" on an enum you encounter some barrier.

6

Interlocked operations on enum are no problem:

public enum State { Idle, Running }

unsafe State CompareExchange(ref State target, State v, State cmp)
{
    fixed (State* p = &target)
        return (State)Interlocked.CompareExchange(ref *(int*)p, (int)v, (int)cmp);
}

See my full answer and discussion at https://stackoverflow.com/a/5589515/147511

5

But are there better ways to do this?

I use a class instead of Enum:

public class DataCollectionManagerState
{
    public static readonly DataCollectionManagerState Off = new DataCollectionManagerState() { };
    public static readonly DataCollectionManagerState Starting = new DataCollectionManagerState() { };
    public static readonly DataCollectionManagerState On = new DataCollectionManagerState() { };

    private DataCollectionManagerState() { }

    public override string ToString()
    {
        if (this == Off) return "Off";
        if (this == Starting) return "Starting";
        if (this == On) return "On";

        throw new Exception();
    }
}

public class DataCollectionManager
{
    private static DataCollectionManagerState _state = DataCollectionManagerState.Off;

    public static void StartDataCollectionManager()
    {
        var originalValue = Interlocked.CompareExchange(ref _state, DataCollectionManagerState.Starting, DataCollectionManagerState.Off);
        if (originalValue != DataCollectionManagerState.Off)
        {
            throw new InvalidOperationException(string.Format("StartDataCollectionManager can be called when it's state is Off only. Current state is \"{0}\".", originalValue.ToString()));
        }

        // Start Data Collection Manager ...

        originalValue = Interlocked.CompareExchange(ref _state, DataCollectionManagerState.On, DataCollectionManagerState.Starting);
        if (originalValue != DataCollectionManagerState.Starting)
        {
            // Your code is really messy
            throw new Exception(string.Format("Unexpected error occurred. Current state is \"{0}\".", originalValue.ToString()));
        }
    }
}
1
  • This is often a good idea if the enum is controlling lots of things as a kind of strategy object. One symptom is lots of switches on the enum in different places. Commented Sep 4, 2013 at 10:06
3

Using System.Runtime.CompilerServices.Unsafe

Here's a pretty good related answer going into depth.

using System;
using System.Runtime.CompilerServices;
using System.Threading;

public static class InterlockedEx
{
    /// <summary>
    /// Enum equivalent of <see cref="Interlocked.CompareExchange(ref Int32, Int32, Int32)"/> and <see cref="Interlocked.CompareExchange(ref Int64, Int64, Int64)"/>
    /// </summary>
    public static TEnum CompareExchange<TEnum>(ref TEnum location, TEnum value, TEnum comparand)
        where TEnum : struct, Enum
    {
        return Unsafe.SizeOf<TEnum>() switch
        {
            // .NET does not support 1- and 2-byte atomic operations as there
            // is no common hardware support for that.
            4 => CompareExchange32Bit(ref location, value, comparand),
            8 => CompareExchange64Bit(ref location, value, comparand),
            _ => throw new NotSupportedException("Only enums with an underlying type of 4 bytes or 8 bytes are allowed to be used with Interlocked")
        };

        static TEnum CompareExchange32Bit(ref TEnum location, TEnum value, TEnum comparand)
        {
            int comparandRaw = Unsafe.As<TEnum, int>(ref comparand);
            int valueRaw = Unsafe.As<TEnum, int>(ref value);
            ref int locationRaw = ref Unsafe.As<TEnum, int>(ref location);
            int returnRaw = Interlocked.CompareExchange(ref locationRaw, valueRaw, comparandRaw);
            return Unsafe.As<int, TEnum>(ref returnRaw);
        }

        static TEnum CompareExchange64Bit(ref TEnum location, TEnum value, TEnum comparand)
        {
            long comparandRaw = Unsafe.As<TEnum, long>(ref comparand);
            long valueRaw = Unsafe.As<TEnum, long>(ref value);
            ref long locationRaw = ref Unsafe.As<TEnum, long>(ref location);
            long returnRaw = Interlocked.CompareExchange(ref locationRaw, valueRaw, comparandRaw);
            return Unsafe.As<long, TEnum>(ref returnRaw);
        }
    }
}
1

I abstracted the Interlocked.CompareExchange away and used a collection of interfaces.

  • ILockManager<T>: Contains all individual locks for each Enum
  • ILock: The lock for an individual Enum
  • ILockReleaser: Responsible for releasing the lock when it is acquired

Interfaces

public interface ILockManager<T> where T : Enum {
    bool TryAcquire(T key, out ILockReleaser lockReleaser);
}

public interface ILock {
    string Name { get; }

    bool TryAcquire(out ILockReleaser lockReleaser);
}

public interface ILockReleaser {
    void Release();
}

Example

You would use this the following (just like a normal Interlock):

enum FooLocks {
    FooOne,
    FooTwo,
}

public class FooClass {
    
    private readonly ILockManager<FooLocks> _lockManager;
    public FooClass(ILockManager<FooLocks> lockManager) {
        _lockManager = lockManager;
    }

    public void RunByOneThreadOnly() {
        if (_lockManager.TryAcquire(FooLocks.FooOne, out var activeLock))
            return;

        try {
            /* Your code */
        } finally {
            activeLock.Release();
        }
    }
}

Implementation

Here is the implementation of the interfaces for ILockManager<T>, ILock and ILockReleaser:

public class LockManager<T> : ILockManager<T> where T : Enum {
    public bool TryAcquire(T key, out ILockReleaser lockReleaser) => _locks[key].TryAcquire(out lockReleaser);

    // Create a lock for each enum value
    private static readonly IReadOnlyDictionary<T, ILock> _locks = new Dictionary<T, ILock>((Enum.GetValues(typeof(T)) as T[]).Select(e => new KeyValuePair<T, ILock>(e, new Lock(e.ToString()))));
}

public class Lock : ILock, ILockReleaser {
    public string Name { get; }

    public Lock(string name) {
        Name = name;
    }
    
    public bool TryAcquire(out ILockReleaser lockReleaser) {
        lockReleaser = this;
        return Interlocked.CompareExchange(ref _curLock, 1, 0) != 0;
    }

    public void Release() {
        Interlocked.Exchange(ref _curLock, 0);
    }

    public override string ToString() => Name;

    private int _curLock;
}

Remarks

This does not support flagged Enums such as FooLocks.FooOne | FooLocks.FooTwo. it would be possible to support them, however, I think that would abstract too much and I would leave it at the consuming class to handle flagged enums.

Not the answer you're looking for? Browse other questions tagged or ask your own question.