2013-08-21 12 views
12

Próbuję użyć Interlocked.CompareExchange z tego wyliczenia:Interlocked.CompareExchange z wyliczenia

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

Poniższy kod nie kompiluje, ale to, co chcę, robię:

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

Sure mogę użyć int zamiast wyliczenia i użyć właściwości:

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

Potem wyrzucił teksty stałe do int:

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

Ale czy istnieją lepsze sposoby na zrobienie tego?

+5

Co które pokazujesz (traktując to jako 'int' i casting) jest w zasadzie dokładnie tym, co robię. –

+0

@MarcGravell: zasadniczo? – joe

+0

Szczerze mówiąc, to nie jest tak wielka sprawa. Osobiście po prostu zatrzymałbym to jako "enum", ale rzuciłem to przed wymianą. – James

Odpowiedz

10

Żeby było proste, nie ma :-)

Niestety C# /. NET rozważyć enum S, typu silnika, częściowo odłączony od ich rodzaju podstawy. Za każdym razem, gdy próbujesz zrobić coś "fantazyjnego" na enum, napotkasz jakąś barierę.

16

Jest to możliwe z poziomu IL i możliwe jest stworzenie dla niego metody pomocniczej, która może być używana z 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); 
    } 
} 

wyjściowa:

 
x: Y 
y: X 

To właśnie przekazuje argumenty do prawidłowego Interlocked.Exchange przeciążenia. Nie działa źle, jeśli T tak naprawdę nie jest typem wyliczeniowym lub jego bazowy typ nie ma przeciążenia Interlocked.Exchange.

Wygenerowany iloraz IL jest weryfikowalny, przynajmniej zgodnie z PEVerify, ponieważ można to sprawdzić, wykorzystując to AssemblyBuilder i zapisując wynik do pliku.

+3

+1 za kod, ale nie polecam korzystania z tego. – DarthVader

+2

@DarthVader Dlaczego nie? Nigdy nie miałem powodu używać "Interlocked.Exchange" z wyliczeniami, ale mam inne przypadki, w których istnieje jasny i poprawny sposób na zrobienie czegoś, CIL pozwala na to, ale C# nie. W tym przypadku nie sądzę, C# jest właściwym narzędziem do pracy, więc nie używam C#. – hvd

+1

Jak niegrzeczny! Ale uwielbiam to i skopiowałem go prosto do mojego kodu :) Teraz możesz również zrobić "Thread.VolatileRead (myEnum)"? –

4

Ale czy istnieją lepsze sposoby na zrobienie tego?

używam klasy zamiast 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())); 
     } 
    } 
} 
+0

Często jest to dobry pomysł, jeśli enum kontroluje wiele rzeczy jako rodzaj obiektu strategii. Jednym z objawów jest wiele przełączników na enum w różnych miejscach. –

+0

Trochę pracy, aby uzyskać wpisane, ale ogólnie dobry pomysł! – joe

3

Interlocked operacji na wyliczenia to żaden 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); 
} 

Zobacz moje pełne odpowiedzi i dyskusję na https://stackoverflow.com/a/5589515/147511