Come verificare se sono stati impostati flag di una combinazione di flag?


180

Diciamo che ho questo enum:

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

Per verificare se, ad esempio, ABè impostato, posso farlo:

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

Esiste un modo più semplice per verificare se una delle flag di una costante di flag combinata è impostata rispetto alla seguente?

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

Potresti ad esempio scambiare il &con qualcosa?

Non troppo stabile quando si tratta di cose binarie come questa ...


Non tutti dovrebbero leggere 'Tutti = A | B | C '?
stevehipwell,

4
AB | C è equivalente ad A | B | C perché AB è stato definito come A | B prima.
Daniel Brückner,

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

Vero. Posso cambiarlo per una migliore lettura.
Svish,

Risposte:


145

Se vuoi sapere se la lettera ha una delle lettere in AB, devi usare l' operatore AND . Qualcosa di simile a:

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

2
Per quanto posso vedere, questo fa il lavoro. E aveva i commenti più chiari. Non viene compilato senza parentesi letter & Letters.AB. Modificato quello lì dentro.
Svish,

Inoltre, se ho introdotto un Letters.None, suppongo che potresti scambiarlo con il 0per un aspetto meno comparabile con il numero magico?
Svish,

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

9
anche stackoverflow.com/questions/40211/how-to-compare-flags-in-c è una risposta raccomandato in quanto i controlli contro l'oggetto in questione invece di controllare se è uguale a 0
Dan Richardson

@danrichardson il problema con il controllo per l'oggetto esatto è che elimina il caso quando viene impostata una parte del valore composto (A o B), che non è ciò che l'OP vuole.
Tom Lint,

181

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

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 mostra il seguente output:

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

14
Questo non affronta la domanda del PO. È comunque necessario && più operazioni hasFlag per determinare se qualsiasi sono impostate bandiere. Quindi la domanda è petsInFamilyha un Pet.Dog || Pet.Cat?
GoClimbColorado

1
Vedi la chiara risposta di Mr. Skeet ... HasFlags Multiple
GoClimbColorado

59

Uso metodi di estensione per 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
Si potrebbe fare un po 'più stretto in questo modo: where T : struct, IConvertible. Ottimo codice altrimenti!
Hamish Grubijan,

@HamishGrubijan, buon punto ... ed enum implementano anche IFormattable e IComparable. Tuttavia, tutti i tipi numerici implementano anche quelle interfacce, quindi non è sufficiente escluderle
Thomas Levesque,

Grazie per la condivisione, ma non è sempre necessario verificare la presenza di enum. IsFlagSet(this Enum value, Enum flag)è sufficiente.
djmj,

34

Esiste il metodo HasFlag in .NET 4 o versioni successive.

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

26

Se è possibile utilizzare .NET 4 o versioni successive rispetto al metodo HasFlag ()

esempi

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

uguale a

letter.HasFlag(Letters.AB)

Sei sicuro di bitwise ORrenderlo "entrambi devono essere impostati" e nessuno?
Staffe

1
bitwise ORcombinerebbe i valori, quindi 1000 | 0010 diventa 1010, o entrambi impostati
Armando,

13

Se ti dà davvero fastidio, puoi 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))
{
   // ...
}

1
La riga return (value & flag) == flag;non viene compilata: "Operatore '&' non può essere applicato agli operandi di tipo 'T' e 'T'" .
Fredrik Mörk,

1
soggezione: la domanda non riguardava le operazioni binarie, la domanda riguardava la semplificazione della sintassi delle operazioni relative alla maschera di bit in C #. Esistono già moltissime eccellenti domande e risposte relative all'operazione binaria su stackoverflow, non è necessario ripubblicarle ovunque.
Tamas Czinege,

Dovrei raccomandare a coloro che non hanno familiarità con le operazioni binarie di familiarizzare, dato che l'impalcatura per nasconderlo sopra rende le cose molto meno leggibili secondo me. Naturalmente la mia soluzione "grezza" di seguito attualmente non sta andando molto bene rispetto al punteggio di questa soluzione, quindi le persone stanno votando le loro preferenze e devo rispettarle ;-)
Sarà il

10

Per verificare se, ad esempio, è impostato AB, posso farlo:

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

Esiste un modo più semplice per verificare se una delle flag di una costante di flag combinata è impostata rispetto alla seguente?

Ciò controlla che siano impostati sia A che B e ignora se sono impostati altri flag.

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

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

Questo può essere semplificato per:

if(letter & Letters.AB)

Ecco la C per le operazioni binarie; dovrebbe essere semplice applicarlo a 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 è la singolare 'lettera', che potrebbe implicare che rappresenti solo una singola lettera; il codice di esempio chiarisce che è un insieme di lettere possibili e che sono consentiti più valori, quindi considera di rinominare la variabile "lettere".


Non sarebbe anything_and_a, a_and_or_c_and_anything_elseed è both_ac_and_anything_elsesempre vero? o mi sto perdendo qualcosa qui?
Svish,

In questo caso, puoi vedere a quali flag è stato inizializzato. Tuttavia, se i flag non contengono A, allora (flags e A) sarebbe 0, il che è falso. both_ac_and_anything_else assicura che siano impostati sia A che C, ma ignora anche qualsiasi altro flag impostato (ad es. è vero se B è impostato o meno).
Sarà il

Hm, alcuni di questi finiscono come numeri e non booleani in C # però. Come li convertiresti in booleani?
Svish,

Non è convertito implicitamente per te? Lo zero equivale a "falso" e tutti gli altri valori sono "veri".
Sarà il

4

Che ne dite di

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

?


Sì! Questo filtrerà i valori A e B e ignorerà se C è incluso. Quindi se è> 0, è anche A o B o AB.
timore del

3
Questo non funziona al 100% con valori firmati. ! = 0 è meglio di> 0 per questo motivo.
Stevehipwell,

4

Ho creato un metodo di estensione semplice che non richiede un controllo sui Enumtipi:

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

Godere!


4

Ci sono molte risposte qui, ma penso che il modo più idiomatico di farlo con Flags sarebbe Letters.AB.HasFlag (letter) o (Letters.A | Letters.B) .HasFlag (letter) se non l'hai fatto ho già Letters.AB. letter.HasFlag (Letters.AB) funziona solo se ha entrambi.


3

Funzionerebbe per te?

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

Saluti,

Sebastiaan


1

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

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

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

Potresti avere lo stesso errore che ho fatto io - lui vuole controllare se è impostato A o B ma ignora C.
Daniel Brückner,

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

Ciò verificherebbe se uno di tutti è stato impostato, non se è stato impostato uno qualsiasi di enum combinato.
Svish,

0

Si potrebbe semplicemente verificare se il valore non è zero.

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

Ma considererei una soluzione migliore introdurre un nuovo valore di enumerazione con valore zero e confrontare nuovamente 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) { }

AGGIORNARE

Ho letto male la domanda - risolto il primo suggerimento e ignora il secondo suggerimento.


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

0

Ci sono due approcci che posso vedere che funzionerebbero per verificare la presenza di eventuali bit impostati.

Aproach A

if (letter != 0)
{
}

Funziona finché non ti dispiace controllare tutti i bit, compresi quelli non definiti!

Aproach B

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

Questo controlla solo i bit definiti, purché Letters.Tutti rappresentino tutti i bit possibili.

Per bit specifici (uno o più set), utilizzare Aproach B in sostituzione di Lettere. Tutti con i bit che si desidera verificare (vedere di seguito).

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

Potresti avere lo stesso errore che ho fatto io - lui vuole controllare se è impostato A o B ma ignora C.
Daniel Brückner,

-1

Siamo spiacenti, ma lo 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 Quindi l'enum contiene 1 + 4

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.