2013-08-21 12 views
12

Sto cercando di utilizzare Interlocked.CompareExchange con questo enum:Interlocked.CompareExchange con enum

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

Il seguente codice non si compila, ma questo è quello che voglio fare fare:

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

Sicuro posso usare un int invece di enum e utilizzare una proprietà:

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

poi gettato le enumerazioni ad un int:

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

Ma ci sono modi migliori per farlo?

+5

Cosa tu mostri (trattandolo come un 'int' e un casting) è fondamentalmente esattamente ciò che faccio. –

+0

@MarcGravell: in pratica? – joe

+0

Per essere onesti non è un vero affare. Personalmente lo terrei come un "enum" ma lo lancio prima di fare lo scambio. – James

risposta

10

Per farla semplice, no :-)

Purtroppo C# /. NET considerare enum s come tipo pieno, in parte staccati dai loro tipo di base. Ogni volta che provi a fare qualcosa di "stravagante" su uno enum, incontri qualche barriera.

16

È possibile da IL, ed è possibile creare un metodo di supporto per questo che può essere utilizzato da 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); 
    } 
} 

uscita:

 
x: Y 
y: X 

Questo inoltra solo gli argomenti alla corretta Interlocked.Exchange sovraccarico. Fallisce male se lo T non è realmente un tipo enum o il suo tipo sottostante non ha un sovraccarico Interlocked.Exchange.

L'IL generato è verificabile, almeno secondo PEVerify, come può essere verificato facendo questo uso AssemblyBuilder e salvando il risultato in un file.

+3

+1 per il codice, ma non consiglierei l'utilizzo di questo. – DarthVader

+2

@DarthVader Perché no? Non ho mai avuto motivo di usare 'Interlocked.Exchange' con le enumerazioni, ma ho altri casi in cui c'è un modo chiaro e corretto di fare qualcosa, CIL lo consente, ma C# no. In tal caso, non penso che C# sia lo strumento giusto per il lavoro, quindi non uso C#. – hvd

+1

Che cattivo! Ma lo adoro e lo copio direttamente nel mio codice :) Ora, puoi anche fare un 'Thread.VolatileRead (myEnum)'? –

4

Ma ci sono modi migliori per farlo?

Io uso una classe invece di 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

Questa è spesso una buona idea se l'enumerazione controlla un sacco di cose come una specie di oggetto strategico. Un sintomo è un sacco di interruttori sull'enumerazione in luoghi diversi. –

+0

Alcuni lavori per essere digitati ma generalmente una buona idea! – joe

3

Interlocked operazioni sul enum non sono un problema:

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

Vedere la mia risposta piena e discussione in https://stackoverflow.com/a/5589515/147511