Подтвердить что ты не робот

Interlocked.CompareExchange с перечислением

Я пытаюсь использовать Interlocked.CompareExchange с этим перечислением:

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

Следующий код не компилируется, но то, что я хочу сделать:

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

Конечно, я могу использовать int вместо enum и использовать свойство:

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

Затем переведите перечисления в int:

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

Но есть ли лучшие способы сделать это?

4b9b3361

Ответ 1

Чтобы сделать его простым, no: -)

Печально С#/.NET считают enum как полный тип, частично отключенный от своего базового типа. Каждый раз, когда вы пытаетесь сделать что-то "причудливое" на enum, вы сталкиваетесь с каким-то барьером.

Ответ 2

Это возможно из IL, и для этого можно создать вспомогательный метод, который можно использовать с С#.

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);
    }
}

Вывод:

x: Y
y: X

Это просто переводит аргументы в правильную перегрузку Interlocked.Exchange. Он плохо работает, если T не является типом перечисления, или его базовый тип не имеет перегрузки Interlocked.Exchange.

Сгенерированный IL проверяется, по крайней мере, согласно PEVerify, как можно проверить, используя это AssemblyBuilder и сохраняя результат в файле.

Ответ 3

Но есть ли лучшие способы сделать это?

Я использую класс вместо 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 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()));
        }
    }
}

Ответ 4

Interlocked операции с перечислением не являются проблемой:

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);
}

Посмотрите мой полный ответ и обсуждение на fooobar.com/info/432378/...