2009-08-27 8 views
124

Diciamo che ho questo enum:Come verificare se sono impostati i flag di una combinazione di flag?

[Flags] 
enum Letters 
{ 
    A = 1, 
    B = 2, 
    C = 4, 
    AB = A | B, 
    All = A | B | C, 
} 

Per verificare se ad esempio AB è impostato posso fare questo:

if((letter & Letters.AB) == Letters.AB) 

C'è un modo più semplice per controllare se una delle bandiere di un la costante di flag combinata è impostata rispetto alla seguente?

if((letter & Letters.A) == Letters.A || (letter & Letters.B) == Letters.B) 

Potrebbe ad esempio sostituire lo & con qualcosa?

Non troppo stabile quando si tratta di roba binario come questo ...

+0

Non dovrebbe leggere tutti 'Tutto = A | B | C '? – stevehipwell

+4

AB | C è equivalente ad A | B | C perché AB è stato definito come A | B prima. –

+0

@Daniel Brückner - È equivalente, ma è meno leggibile. Soprattutto se l'enum è stato ampliato. – stevehipwell

risposta

92

Se vuoi sapere se la lettera è una delle lettere di AB è necessario utilizzare l'operatore AND &. Qualcosa di simile:

if ((letter & Letters.AB) != 0) 
{ 
    // Some flag (A,B or both) is enabled 
} 
else 
{ 
    // None of them are enabled 
} 
+1

Per quanto posso vedere, questo fa il lavoro. E ha avuto i commenti più chiari.Non si compila però senza una parentesi attorno a 'letter & Letters.AB'. Modificato che lì dentro. – Svish

+0

Anche se avessi introdotto un 'Letters.None', presumo che potresti scambiarlo con' 0' per un look con meno confronti con numero magico? – Svish

+0

Ovviamente. Ma non penso che il confronto AND con 0 possa essere pensato come un numero magico rigorosamente. – yeyeyerman

0
if((int)letter != 0) { } 
+0

Potrebbe essere lo stesso errore che ho fatto io: vuole controllare se A o B è impostato ma ignorare C. –

+0

Non hai bisogno del cast se stai controllando l'enum contro 0. – stevehipwell

+0

Questo verificherebbe se uno di tutti di essi è stato impostato, non se è stato impostato alcun enum combinato. – Svish

3

Come su

if ((letter & Letters.AB) > 0) 

?

+0

Sì! Questo filtrerà sui valori A e B e ignorerà se C è incluso. Quindi se è> 0, è anche A o B o AB. – awe

+3

Questo non funziona al 100% con i valori firmati. ! = 0 è migliore di> 0 per questo motivo. – stevehipwell

9

Se davvero ti dà fastidio, è possibile scrivere una funzione del genere:

public bool IsSet(Letters value, Letters flag) 
{ 
    return (value & flag) == flag; 
} 

if (IsSet(letter, Letters.A)) 
{ 
    // ... 
} 

// If you want to check if BOTH Letters.A and Letters.B are set: 
if (IsSet(letter, Letters.A & Letters.B)) 
{ 
    // ... 
} 

// If you want an OR, I'm afraid you will have to be more verbose: 
if (IsSet(letter, Letters.A) || IsSet(letter, Letters.B)) 
{ 
    // ... 
} 
+0

La riga 'return (valore e flag) == flag;' non compila: * "Operator '&' non può essere applicato agli operandi di tipo 'T' e 'T'" *. –

+0

è necessario eseguire il casting per un primo tempo ... –

+0

Questo non lo aiuta con la comprensione delle operazioni binarie ... – awe

2

Questo lavoro potrebbe funzionare?

if ((letter & (Letters.A | Letters.B)) != 0) 

saluti,

Sebastiaan

5

per verificare se ad esempio AB è impostata posso fare questo:

if ((lettera & Letters.AB) == Letters. AB)

Esiste un modo più semplice per verificare se uno dei flag di una costante di flag combinata è impostato rispetto a f susseguente?

Questo controlla che sia A e B sono impostate, e ignora se eventuali altre bandiere sono impostati.

if((letter & Letters.A) == Letters.A || (letter & Letters.B) == Letters.B) 

Questo controlla che sia A o B è impostato, e ignora se eventuali altri flag sono impostati o meno.

Questo può essere semplificata:

if(letter & Letters.AB) 

Ecco il C per operazioni binarie; dovrebbe essere semplice da applicare questo per C#:

enum { 
    A = 1, 
    B = 2, 
    C = 4, 
    AB = A | B, 
    All = AB | C, 
}; 

int flags = A|C; 

bool anything_and_a = flags & A; 

bool only_a = (flags == A); 

bool a_and_or_c_and_anything_else = flags & (A|C); 

bool both_ac_and_anything_else = (flags & (A|C)) == (A|C); 

bool only_a_and_c = (flags == (A|C)); 

Per inciso, la denominazione della variabile nell'esempio della domanda è il singolare 'lettera', il che potrebbe implicare che essa rappresenta solo una singola lettera; il codice di esempio chiarisce che è un insieme di possibili lettere e che sono consentiti più valori, quindi considera di rinominare la variabile "lettere".

+0

Non sarebbe sempre vero? Anything_and_a', 'a_and_or_c_and_anything_else' e' both_ac_and_anything_else'? o mi sto perdendo qualcosa qui? – Svish

+0

In questo caso, è possibile vedere quali flag sono stati inizializzati. Tuttavia, se i flag non contengono A, allora (flags & A) sarebbe 0, che è falso. both_ac_and_anything_else assicura che sia A sia C siano impostati, ma ignora tutti gli altri flag che sono anche impostati (ad es. è vero che B sia impostato o meno). – Will

+0

Hm, alcuni di questi finiscono come numeri e non booleani in C#. Come li convertirai in booleano? – Svish

0

Si potrebbe semplicemente verificare se il valore non è zero.

if ((Int32)(letter & Letters.AB) != 0) { } 

Ma io lo considero una soluzione migliore per introdurre un nuovo valore di enumerazione con valore zero e confrontare agains questo valore di enumerazione (se possibile, perché si deve essere in grado di modificare l'enumerazione).

[Flags] 
enum Letters 
{ 
    None = 0, 
    A = 1, 
    B = 2, 
    C = 4, 
    AB = A | B, 
    All = AB | C 
} 

if (letter != Letters.None) { } 

UPDATE

Missread la questione - ha fissato il primo suggerimento e basta ignorare il secondo suggerimento.

+0

Non hai bisogno del cast se stai controllando l'enum contro 0. – stevehipwell

0

Ci sono due approcci che posso vedere che funzionerebbero per il controllo di eventuali bit impostati.

Aproach Un

if (letter != 0) 
{ 
} 

Questo funziona finchè non vi occupate di controllo per tutti i bit, inclusi quelli non definiti troppo!

Aproach B

if ((letter & Letters.All) != 0) 
{ 
} 

Ciò solo controlla i bit definiti, purché Letters.All rappresenta tutte le possibili bit.

Per bit specifici (uno o più set), utilizzare Aproach B sostituendo Letters.Tutti i bit che si desidera controllare (vedere di seguito).

if ((letter & Letters.AB) != 0) 
{ 
} 
+0

Potresti commettere lo stesso errore di me: vuole controllare se A o B è impostato ma ignorare C. –

54

Io uso i metodi di estensione di scrivere cose del genere:

if (letter.IsFlagSet(Letter.AB)) 
    ... 

Ecco il codice:

public static class EnumExtensions 
{ 
    private static void CheckIsEnum<T>(bool withFlags) 
    { 
     if (!typeof(T).IsEnum) 
      throw new ArgumentException(string.Format("Type '{0}' is not an enum", typeof(T).FullName)); 
     if (withFlags && !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 
    { 
     CheckIsEnum<T>(true); 
     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 
    { 
     CheckIsEnum<T>(true); 
     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 
    { 
     CheckIsEnum<T>(true); 
     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 
    { 
     CheckIsEnum<T>(true); 
     long lValue = 0; 
     foreach (T flag in flags) 
     { 
      long lFlag = Convert.ToInt64(flag); 
      lValue |= lFlag; 
     } 
     return (T)Enum.ToObject(typeof(T), lValue); 
    } 

    public static string GetDescription<T>(this T value) where T : struct 
    { 
     CheckIsEnum<T>(false); 
     string name = Enum.GetName(typeof(T), value); 
     if (name != null) 
     { 
      FieldInfo field = typeof(T).GetField(name); 
      if (field != null) 
      { 
       DescriptionAttribute attr = Attribute.GetCustomAttribute(field, typeof(DescriptionAttribute)) as DescriptionAttribute; 
       if (attr != null) 
       { 
        return attr.Description; 
       } 
      } 
     } 
     return null; 
    } 
} 
+1

Potresti renderlo un po 'più stretto così: 'dove T: struct, IConvertible'. Ottimo codice altrimenti! –

+0

@HamishGrubijan, buon punto ... e le enumerazioni implementano anche IFormattable e IComparable. Tuttavia, tutti i tipi numerici implementano anche tali interfacce, quindi non è sufficiente escluderli. –

+3

Nota: rende allocazioni –

122

In .NET 4 è possibile utilizzare il Enum.HasFlag method:

using System; 

[Flags] public enum Pet { 
    None = 0, 
    Dog = 1, 
    Cat = 2, 
    Bird = 4, 
    Rabbit = 8, 
    Other = 16 
} 

public class Example 
{ 
    public static void Main() 
    { 
     // Define three families: one without pets, one with dog + cat and one with a dog only 
     Pet[] petsInFamilies = { Pet.None, Pet.Dog | Pet.Cat, Pet.Dog }; 
     int familiesWithoutPets = 0; 
     int familiesWithDog = 0; 

     foreach (Pet petsInFamily in petsInFamilies) 
     { 
     // Count families that have no pets. 
     if (petsInFamily.Equals(Pet.None)) 
      familiesWithoutPets++; 
     // Of families with pets, count families that have a dog. 
     else if (petsInFamily.HasFlag(Pet.Dog)) 
      familiesWithDog++; 
     } 
     Console.WriteLine("{0} of {1} families in the sample have no pets.", 
         familiesWithoutPets, petsInFamilies.Length); 
     Console.WriteLine("{0} of {1} families in the sample have a dog.", 
         familiesWithDog, petsInFamilies.Length); 
    } 
} 

L'esempio visualizza il seguente output:

//  1 of 3 families in the sample have no pets. 
//  2 of 3 families in the sample have a dog. 
+7

Questo non indirizza la domanda OP. È necessario ancora && più operazioni HasFlag per determinare se * qualsiasi * flag è impostato. Quindi la domanda è: 'PetsInFamily' ha un' Pet.Dog || Pet.Cat'? – GoClimbColorado

+0

Vedere la risposta chiara del signor Skeet ... [HasFlags Multiple] (http://stackoverflow.com/a/7984448/42239) – GoClimbColorado

20

C'è il metodo HasFlag in .NET 4 o versione successiva.

if(letter.HasFlag(Letters.AB)) 
{ 
} 
14

Se è possibile utilizzare .NET 4 o superiore uso hasFlag() metodo

esempi

letter.HasFlag(Letters.A | Letters.B) // both A and B must be set 

stessa

letter.HasFlag(Letters.AB) 
1

ho creato un metodo di estensione semplice che non ha bisogno di un controllo sui tipi Enum:

public static bool HasAnyFlag(this Enum value, Enum flags) 
{ 
    return 
     value != null && ((Convert.ToInt32(value) & Convert.ToInt32(flags)) != 0); 
} 

Funziona anche su enumerazioni nullable. Il metodo standard HasFlag no, quindi ho creato un'estensione per coprire anche quello.

public static bool HasFlag(this Enum value, Enum flags) 
{ 
    int f = Convert.ToInt32(flags); 

    return 
     value != null && ((Convert.ToInt32(value) & f) == f); 
} 

Un semplice test:

[Flags] 
enum Option 
{ 
    None = 0x00, 
    One = 0x01, 
    Two = 0x02, 
    Three = One | Two, 
    Four = 0x04 
} 

[TestMethod] 
public void HasAnyFlag() 
{ 
    Option o1 = Option.One; 
    Assert.AreEqual(true, o1.HasAnyFlag(Option.Three)); 
    Assert.AreEqual(false, o1.HasFlag(Option.Three)); 

    o1 |= Option.Two; 
    Assert.AreEqual(true, o1.HasAnyFlag(Option.Three)); 
    Assert.AreEqual(true, o1.HasFlag(Option.Three)); 
} 

[TestMethod] 
public void HasAnyFlag_NullableEnum() 
{ 
    Option? o1 = Option.One; 
    Assert.AreEqual(true, o1.HasAnyFlag(Option.Three)); 
    Assert.AreEqual(false, o1.HasFlag(Option.Three)); 

    o1 |= Option.Two; 
    Assert.AreEqual(true, o1.HasAnyFlag(Option.Three)); 
    Assert.AreEqual(true, o1.HasFlag(Option.Three)); 
} 

Enjoy!

2

Qui ci sono molte risposte ma penso che il modo più idiomatico per farlo con le Bandiere sarebbe Letters.AB.HasFlag (lettera) o (Letters.A | Letters.B) .HasFlag (lettera) se non hai già Letters.AB. letter.HasFlag (Letters.AB) funziona solo se ha entrambi.

0

dispiace, ma io mostrerò in VB :)

<Flags()> Public Enum Cnt As Integer 
     None = 0 
     One = 1 
     Two = 2 
     Three = 4 
     Four = 8  
    End Enum 

    Sub Test() 
    Dim CntValue As New Cnt 
    CntValue += Cnt.One 
    CntValue += Cnt.Three 
    Console.WriteLine(CntValue) 
    End Sub 

CntValue = 5 Così l'enum contiene 1 + 4

0

È possibile utilizzare questo metodo di estensione su enum, per qualsiasi tipo di enums:

public static bool IsSingle(this Enum value) 
{ 
    var items = Enum.GetValues(value.GetType()); 
    var counter = 0; 
    foreach (var item in items) 
    { 
     if (value.HasFlag((Enum)item)) 
     { 
      counter++; 
     } 
     if (counter > 1) 
     { 
      return false; 
     } 
    } 
    return true; 
}