2009-07-06 14 views
13

Devo rilevare se un flag è impostato all'interno di un valore enum, quale tipo è contrassegnato con l'attributo Flag.Confronto tra flag enum in C#

Di solito è fatto così:

(value & flag) == flag 

Ma dal momento che ho bisogno di fare questo generici (a volte a caso runtime ho solo un riferimento "Enum" Non riesco a trovare un modo semplice da usare. . l'operatore & al momento mi rendono in questo modo:

public static bool IsSet<T>(this T value, T flags) where T : Enum 
    { 
     Type numberType = Enum.GetUnderlyingType(typeof(T)); 

     if (numberType.Equals(typeof(int))) 
     { 
      return BoxUnbox<int>(value, flags, (a, b) => (a & b) == b); 
     } 
     else if (numberType.Equals(typeof(sbyte))) 
     { 
      return BoxUnbox<sbyte>(value, flags, (a, b) => (a & b) == b); 
     } 
     else if (numberType.Equals(typeof(byte))) 
     { 
      return BoxUnbox<byte>(value, flags, (a, b) => (a & b) == b); 
     } 
     else if (numberType.Equals(typeof(short))) 
     { 
      return BoxUnbox<short>(value, flags, (a, b) => (a & b) == b); 
     } 
     else if (numberType.Equals(typeof(ushort))) 
     { 
      return BoxUnbox<ushort>(value, flags, (a, b) => (a & b) == b); 
     } 
     else if (numberType.Equals(typeof(uint))) 
     { 
      return BoxUnbox<uint>(value, flags, (a, b) => (a & b) == b); 
     } 
     else if (numberType.Equals(typeof(long))) 
     { 
      return BoxUnbox<long>(value, flags, (a, b) => (a & b) == b); 
     } 
     else if (numberType.Equals(typeof(ulong))) 
     { 
      return BoxUnbox<ulong>(value, flags, (a, b) => (a & b) == b); 
     } 
     else if (numberType.Equals(typeof(char))) 
     { 
      return BoxUnbox<char>(value, flags, (a, b) => (a & b) == b); 
     } 
     else 
     { 
      throw new ArgumentException("Unknown enum underlying type " + numberType.Name + "."); 
     } 
    } 


    private static bool BoxUnbox<T>(object value, object flags, Func<T, T, bool> op) 
    { 
     return op((T)value, (T)flags); 
    } 

Ma non mi piace il senza fine, se - blocchi altro, quindi c'è un modo per lanciare questi valori che posso utilizzare l'operatore & o qualsiasi altra soluzione per verificare questo?

+0

Una domanda simile può essere trovata qui: http://stackoverflow.com/questions/746905/get-integral-value-of-boxed-enum – Kirtan

+0

Ancora un altro motivo per cui C# ha bisogno di un vincolo enum generico: http://stackoverflow.com/questions/7244 – Keith

risposta

6

Personalmente, penso che abbia un bell'aspetto perché lo hai avvolto in un'unica funzione. Se avessi quel codice sparsi in un intero programma penso che avresti dei problemi, ma ciò che hai creato migliora la chiarezza ovunque sia usato e la funzione stessa è abbastanza chiara su ciò che fa.

Solo la mia opinione ovviamente.

Si potrebbe, però, utilizzare il è parola chiave, che potrebbe aiutare un po '

public static bool IsSet<T>(this T value, T flags) where T : Enum 
{ 
    if (value is int) 
    { 
     return ((int)(object)a & (int)(object)b) == (int)(object)b); 
    } 
    //etc... 
+1

Troppi getti non necessari. – arbiter

+1

FWIW, non è possibile eseguire il cast di un tipo generico direttamente su un tipo di valore, quindi il casting dell'oggetto aggiuntivo. – Hugoware

+0

Sì, colpa mia. Ho dimenticato che questo è il motivo per cui ho usato la classe Convert anziché la trasmissione diretta :) – arbiter

7

Questo dovrebbe fare il lavoro per enum tipi con tutti i tipi di fondo:

public static bool IsSet<T>(this T value, T flags) where T : struct 
{ 
    return (Convert.ToInt64(value) & Convert.ToInt64(flags)) == 
     Convert.ToInt64(flags); 
} 

Convert.ToInt64 viene utilizzato perché un Il numero intero a 64 bit è il tipo integrale "più largo" possibile, al quale possono essere espressi tutti i valori enum (anche ulong). Nota che char non è un tipo sottostante valido. Sembra che sia non valido in C#, ma è è in generale valido in CIL/per CLR.

Inoltre, non è possibile imporre un vincolo di tipo generico per le enumerazioni (ad esempio where T : struct); il meglio che puoi fare è utilizzare where T : struct per applicare il valore T a un tipo di valore, quindi facoltativamente eseguire un controllo dinamico per garantire che T sia un tipo enum.

Per completezza, ecco la mia molto breve test harness:

static class Program 
{ 
    static void Main(string[] args) 
    { 
     Debug.Assert(Foo.abc.IsSet(Foo.abc)); 
     Debug.Assert(Bar.def.IsSet(Bar.def)); 
     Debug.Assert(Baz.ghi.IsSet(Baz.ghi)); 
    } 

    enum Foo : int 
    { 
     abc = 1, 
     def = 10, 
     ghi = 100 
    } 

    enum Bar : sbyte 
    { 
     abc = 1, 
     def = 10, 
     ghi = 100 
    } 

    enum Baz : ulong 
    { 
     abc = 1, 
     def = 10, 
     ghi = 100 
    } 
} 
+0

Istruzione non corretta, perché i flag possono avere più di un bit impostato. – arbiter

+0

Char non è un tipo valido sottostante ... in C#. La sua domanda non dice chi ha creato l'enum e in quale lingua. – sisve

+0

Ho rimosso il mio commento originale, ma non sono ancora convinto che questo sia assolutamente corretto poiché il lato destro del test è ancora un Enum. Devo anche essere convertito, credo. – tvanfosson

0

Ho usato questo per confrontare le bandiere

public static bool IsSet<T>(this T input, T match) 
{ 
    return (Convert.ToUInt32(input) & Convert.ToUInt32(match)) != 0; 
} 

Qui si possono fare le varie conversioni. Da int a breve a lungo.

+0

Dichiarazione non corretta pure. – arbiter

15

Per me sembra eccessivamente complicato.Che ne dite di questo (tenendo presente che enum è sempre associato a un tipo valore intero):

public static bool IsSet<T>(T value, T flags) where T : struct 
{ 
    // You can add enum type checking to be perfectly sure that T is enum, this have some cost however 
    // if (!typeof(T).IsEnum) 
    //  throw new ArgumentException(); 
    long longFlags = Convert.ToInt64(flags); 
    return (Convert.ToInt64(value) & longFlags) == longFlags; 
} 
+0

Sì, quindi questo è praticamente lo stesso del mio ora. Non sono sicuro che mi preoccuperei di scomporre il 'Convert.ToInt64 (flags)' comunque, dato che il compilatore C# lo ottimizzerebbe comunque. – Noldorin

+0

@Noldorin, ovviamente questo è piuttosto semplice, anche banale, ecco perché così tante risposte qui :) – arbiter

+0

@tvanfosson: È più breve lasciare fuori la dichiarazione delle variabili, qualsiasi non meno leggibile, secondo me. Questo è comunque pedanteria e semantica ... – Noldorin

10

ho scritto una serie di metodi di estensione per le enumerazioni, in caso di necessità:

public static class EnumExtensions 
{ 
    private static void CheckEnumWithFlags<T>() 
    { 
     if (!typeof(T).IsEnum) 
      throw new ArgumentException(string.Format("Type '{0}' is not an enum", typeof(T).FullName)); 
     if (!Attribute.IsDefined(typeof(T), typeof(FlagsAttribute))) 
      throw new ArgumentException(string.Format("Type '{0}' doesn't have the 'Flags' attribute", typeof(T).FullName)); 
    } 

    public static bool IsFlagSet<T>(this T value, T flag) where T : struct 
    { 
     CheckEnumWithFlags<T>(); 
     long lValue = Convert.ToInt64(value); 
     long lFlag = Convert.ToInt64(flag); 
     return (lValue & lFlag) != 0; 
    } 

    public static IEnumerable<T> GetFlags<T>(this T value) where T : struct 
    { 
     CheckEnumWithFlags<T>(); 
     foreach (T flag in Enum.GetValues(typeof(T)).Cast<T>()) 
     { 
      if (value.IsFlagSet(flag)) 
       yield return flag; 
     } 
    } 

    public static T SetFlags<T>(this T value, T flags, bool on) where T : struct 
    { 
     CheckEnumWithFlags<T>(); 
     long lValue = Convert.ToInt64(value); 
     long lFlag = Convert.ToInt64(flags); 
     if (on) 
     { 
      lValue |= lFlag; 
     } 
     else 
     { 
      lValue &= (~lFlag); 
     } 
     return (T)Enum.ToObject(typeof(T), lValue); 
    } 

    public static T SetFlags<T>(this T value, T flags) where T : struct 
    { 
     return value.SetFlags(flags, true); 
    } 

    public static T ClearFlags<T>(this T value, T flags) where T : struct 
    { 
     return value.SetFlags(flags, false); 
    } 

    public static T CombineFlags<T>(this IEnumerable<T> flags) where T : struct 
    { 
     CheckEnumWithFlags<T>(); 
     long lValue = 0; 
     foreach (T flag in flags) 
     { 
      long lFlag = Convert.ToInt64(flag); 
      lValue |= lFlag; 
     } 
     return (T)Enum.ToObject(typeof(T), lValue); 
    } 
} 

Il svantaggio principale è che non è possibile specificare where T : Enum: è esplicitamente vietato ("Vincolo non può essere una classe speciale 'System.Enum'"), quindi i metodi di estensione appariranno in intellisense per tutte le strutture ... Ho aggiunto il metodo CheckEnumWithFlags a controlla che il tipo sia effettivamente un enum e abbia l'attributo Flags.


UPDATE: Jon Skeet recentemente iniziato una libreria interessante chiamato UnconstrainedMelody che fa esattamente lo stesso tipo di cose, e lavora intorno alla limitazione tipo di vincolo generico di cui sopra

+0

[+1] Stavo cercando un modo per enumerare oltre i flag impostati; il mio codice utilizza la stessa tecnica dietro il metodo GetFlags solo che non è un'estensione. È triste che C# non possa lasciare specificare "where T: Enum". Senza il metodo di estensione puoi semplicemente usare HasFlag() che è incorporato. –

0

o ... public static bool isset (questo valore Enum, confrontare Enum) { int baseValue = value.ToInt32(); int compareValue = compare.ToInt32(); if (baseValue == 0) return false; return ((baseValue & compareValore) == compareValore); }

2

Utilizzare semplicemente il metodo Enum.HasFlag()!

+0

Sì, ora con .net4.0;) ma devi usarlo con cura, perché è molto lento. – Enyra

+0

Il problema è quando si confronta un set di flag con un altro gruppo di flag ... se si usano solo gli operandi, è possibile scoprire se tutti i flag corrispondono, o se è impostato un particolare flag (HasFlag fa anche questo). .. ma diventa complicato quando ci si preoccupa solo se uno dei valori di flag è presente nell'altro set di valori di flag ... Il metodo di Thomas GetFlags ci aiuta a farlo. –