带枚举的Interlocked.CompareExchange

joe*_*joe 17 c# interlocked

我正在尝试使用Interlocked.CompareExchange这个枚举:

public enum State {
    Idle,
    Running,
    //...
}
Run Code Online (Sandbox Code Playgroud)

以下代码无法编译,但这就是我想要做的:

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

当然我可以使用int而不是枚举并使用属性:

private int state = (int)State.Idle;
public State { get { return (State)state; } }
Run Code Online (Sandbox Code Playgroud)

然后将枚举转换为int:

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

但有没有更好的方法来做到这一点?

小智 18

IL可以实现,并且可以为此创建一个可以从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);
    }
}
Run Code Online (Sandbox Code Playgroud)

输出:

x: Y
y: X

这只是将参数转发给正确的Interlocked.Exchange重载.如果T它不是真正的枚举类型,或者它的基础类型没有Interlocked.Exchange重载,它会严重失败.

生成的IL是可验证的,至少根据PEVerify,可以通过使用AssemblyBuilder并将结果保存到文件来检查.

  • +1代码,但我不建议使用此. (3认同)
  • @DarthVader为什么不呢?我没有理由在枚举中使用`Interlocked.Exchange`,但我确实有其他情况,有一个明确和正确的方法来做某事,CIL允许它,但C#没有.在这种情况下,我不认为C#是工作的正确工具,所以我不使用C#. (2认同)

xan*_*tos 12

为简单起见,没有:-)

可悲的是,C#/ .NET将enums视为完整类型,部分与其基类型断开连接.每次你尝试做一些"花哨"的事情,enum你会遇到一些障碍.


小智 5

但是,有更好的方法吗?

我使用一个类而不是枚举:

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()));
        }
    }
}
Run Code Online (Sandbox Code Playgroud)


Gle*_*den 5

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);
}
Run Code Online (Sandbox Code Playgroud)

/sf/answers/391266081/上查看我的完整答案和讨论