我正在尝试将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");
}
但有更好的方法吗?
答案 0 :(得分:17)
可以从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);
}
}
输出:
x: Y y: X
这只是将参数转发给正确的Interlocked.Exchange
重载。如果T
实际上不是枚举类型,或者其基础类型没有Interlocked.Exchange
重载,则会严重失败。
生成的IL是可验证的,至少根据PEVerify,可以通过使用AssemblyBuilder
并将结果保存到文件来检查。
答案 1 :(得分:12)
为简单起见,不要: - )
可悲的是,C#/ .NET将enum
视为完整类型,与其基本类型部分断开连接。每当你试图在enum
上做一些“幻想”时,你会遇到一些障碍。
答案 2 :(得分:4)
但有更好的方法吗?
我使用的是一个类而不是枚举:
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()));
}
}
}
答案 3 :(得分: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);
}
上查看我的完整答案和讨论
答案 4 :(得分:1)
System.Runtime.CompilerServices.Unsafe
Here's一个很好的相关答案正在深入。
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 : 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);
}
}
}