¿Cómo verificar si se configuran las banderas de una combinación de banderas?

180

Digamos que tengo esta enumeración:

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

Para verificar si, por ejemplo, ABestá configurado, puedo hacer esto:

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

¿Hay alguna manera más simple de verificar si alguna de las banderas de una constante de bandera combinada está establecida que la siguiente?

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

¿Podría, por ejemplo, cambiar el &por algo?

No es demasiado estable cuando se trata de cosas binarias como esta ...

Svish
fuente
No todos deberían leer 'Todos = A | B | C'?
stevehipwell
44
AB | C es equivalente a A | B | C porque AB se definió como A | B antes.
Daniel Brückner
1
@Daniel Brückner: es equivalente, pero es menos legible. Especialmente si la enumeración se expandió.
stevehipwell
Cierto. Puedo cambiarlo para una mejor lectura.
Svish

Respuestas:

145

Si desea saber si la letra tiene alguna de las letras en AB, debe usar el operador AND &. Algo como:

if ((letter & Letters.AB) != 0)
{
    // Some flag (A,B or both) is enabled
}
else
{
    // None of them are enabled
}
yeyeyerman
fuente
2
Por lo que puedo ver, esto hace el trabajo. Y tuvo los comentarios más claros. Sin embargo, no se compila sin un paréntesis letter & Letters.AB. Editado eso allí.
Svish
Además, si introduje un Letters.None, ¿supongo que podría cambiar eso 0por un aspecto menos comparativo con el número mágico?
Svish
Por supuesto. Pero no creo que la comparación AND con 0 pueda considerarse estrictamente como un número mágico.
yeyeyerman
9
También stackoverflow.com/questions/40211/how-to-compare-flags-in-c es una respuesta recomendada, ya que se compara con el elemento en cuestión en lugar de comprobar si es igual a 0
dan richardson
@danrichardson, el problema con la verificación del elemento exacto es que elimina el caso cuando se establece una parte del valor compuesto (A o B), que no es lo que quiere el OP.
Tom Lint
181

En .NET 4 puede usar el método 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);   
   }
}

El ejemplo muestra el siguiente resultado:

//       1 of 3 families in the sample have no pets. 
//       2 of 3 families in the sample have a dog.
Chuck Kasabula
fuente
14
Esto no aborda la pregunta OP. Debe todavía && múltiples operaciones HasFlag para determinar si alguna se establecen las banderas. Entonces la pregunta es ¿ petsInFamilytiene o un Pet.Dog || Pet.Cat?
GoClimbColorado
1
Vea la respuesta clara del Sr. Skeet ... HasFlags Multiple
GoClimbColorado
59

Utilizo métodos de extensión para escribir cosas así:

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

Aquí está el código:

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;
    }
}
Thomas Levesque
fuente
1
Se podría hacer un poco más fuerte de este modo: where T : struct, IConvertible. Gran código de lo contrario!
Hamish Grubijan
@HamishGrubijan, buen punto ... y enumeraciones también implementan IFormattable e IComparable. Sin embargo, todos los tipos numéricos también implementan esas interfaces, por lo que no es suficiente excluirlas
Thomas Levesque
Gracias por compartir, pero no siempre necesita verificar la enumeración. IsFlagSet(this Enum value, Enum flag)es suficiente.
djmj
34

Hay un método HasFlag en .NET 4 o superior.

if(letter.HasFlag(Letters.AB))
{
}
Artru
fuente
26

Si puede usar .NET 4 o superior que el método HasFlag ()

ejemplos

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

igual que

letter.HasFlag(Letters.AB)
Luka
fuente
¿Estás seguro de que bitwise ORhace "ambos deben estar configurados" y ninguno?
Brackets
1
bitwise ORcombinaría los valores, entonces 1000 | 0010 se convierte en 1010, o ambos conjuntos
Armando
13

Si realmente te molesta, puedes escribir una función como esta:

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))
{
   // ...
}
Tamas Czinege
fuente
1
La línea return (value & flag) == flag;no se compila: "Operador 'y' no se pueden aplicar a operandos de tipo 'T' y 'T'" .
Fredrik Mörk
1
asombro: la pregunta no era sobre operaciones binarias, sino sobre simplificar la sintaxis de las operaciones relacionadas con la máscara de bits en C #. Ya hay muchas preguntas y respuestas excelentes relacionadas con la operación binaria en stackoverflow, no hay necesidad de volver a publicarlas en todas partes.
Tamas Czinege
Debo recomendar que los que no estén familiarizados con las operaciones binarias se familiaricen, ya que el andamiaje para ocultarlo en realidad hace que las cosas sean mucho menos legibles en mi opinión. Por supuesto, mi solución 'en bruto' a continuación no está funcionando bien en comparación con el puntaje de esta solución, por lo que las personas votan sus preferencias y tengo que respetar eso ;-)
Será el
10

Para verificar si, por ejemplo, AB está configurado, puedo hacer esto:

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

¿Hay alguna manera más simple de verificar si alguna de las banderas de una constante de bandera combinada está establecida que la siguiente?

Esto verifica que tanto A como B estén configurados, e ignora si hay otros indicadores configurados.

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

Esto comprueba que o bien se fija A o B, e ignora si otros banderas se establecen o no.

Esto se puede simplificar para:

if(letter & Letters.AB)

Aquí está la C para operaciones binarias; debería ser sencillo aplicar esto 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));

Por cierto, el nombre de la variable en el ejemplo de la pregunta es la 'letra' singular, lo que podría implicar que representa solo una letra; el código de ejemplo deja en claro que es un conjunto de letras posibles y que se permiten múltiples valores, así que considere renombrar la variable 'letras'.

Será
fuente
No sería anything_and_a, a_and_or_c_and_anything_elsey both_ac_and_anything_elsesiempre ser verdad? o me estoy perdiendo algo aquí?
Svish
En este caso, puede ver a qué banderas se ha inicializado. Sin embargo, si las banderas no contienen A, entonces (banderas y A) serían 0, lo cual es falso. both_ac_and_anything_else asegura que tanto A como C estén configurados, pero ignora cualquier otro indicador que también esté configurado (por ejemplo, es cierto si B está configurado o no).
Será
Hm, algunos de esos terminan como números y no booleanos en C #. ¿Cómo los convertirías a booleanos?
Svish
¿No está implícitamente convertido para ti? Cero es equivalente a 'falso', y todos los demás valores son 'verdadero'.
Será
4

Qué tal si

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

?

Jakob Christensen
fuente
¡Si! Esto filtrará los valores A y B e ignorará si se incluye C. Entonces, si es> 0, también es A o B o AB.
asombro
3
Esto no funciona al 100% con valores firmados. ! = 0 es mejor que> 0 por este motivo.
stevehipwell
4

Creé un método de extensión simple que no necesita verificar los Enumtipos:

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

También funciona en enumeraciones anulables. El HasFlagmétodo estándar no funciona, así que creé una extensión para cubrir eso también.

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

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

Una prueba simple:

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

¡Disfrutar!

Henk van Boeijen
fuente
4

Aquí hay muchas respuestas, pero creo que la forma más idiomática de hacer esto con Flags sería Letters.AB.HasFlag (letra) o (Letters.A | Letters.B) .HasFlag (letra) si no lo hicieras ya tengo Letters.AB. letter.HasFlag (Letters.AB) solo funciona si tiene ambas.

Novaterata
fuente
3

Que este trabajo para usted?

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

Saludos,

Sebastiaan

Sebastiaan M
fuente
1

Puede usar este método de extensión en la enumeración, para cualquier tipo de enumeración:

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;
}
masehhat
fuente
0
if((int)letter != 0) { }
Sotavento
fuente
Puede que cometas el mismo error que yo: quiere comprobar si A o B están configurados, pero ignora a C.
Daniel Brückner
No es necesario el reparto si usted está comprobando la enumeración contra 0.
stevehipwell
Esto verificaría si alguno de ellos estaba configurado, no si alguno de una enumeración combinada estaba configurada.
Svish
0

Simplemente puede verificar si el valor no es cero.

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

Pero consideraría una mejor solución introducir un nuevo valor de enumeración con valor cero y comparar de nuevo este valor de enumeración (si es posible porque debe poder modificar la enumeración).

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

if (letter != Letters.None) { }

ACTUALIZAR

No leí la pregunta: arreglé la primera sugerencia e ignore la segunda sugerencia.

Daniel Brückner
fuente
No necesita el elenco si está comprobando la enumeración en contra de 0.
stevehipwell
0

Hay dos enfoques que puedo ver que funcionarían para verificar cualquier bit que se establezca.

Aproximación A

if (letter != 0)
{
}

¡Esto funciona siempre y cuando no le importe comprobar todos los bits, incluidos los no definidos también!

Aproximación B

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

Esto solo verifica los bits definidos, siempre y cuando Letters. Todos represente todos los bits posibles.

Para bits específicos (uno o más conjuntos), use Aproach B reemplazando las letras. Todos con los bits que desea verificar (ver más abajo).

if ((letter & Letters.AB) != 0)
{
}
stevehipwell
fuente
Puede que cometas el mismo error que yo: quiere comprobar si A o B están configurados, pero ignora C.
Daniel Brückner
-1

Lo siento, pero lo mostraré en 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 Entonces la enumeración contiene 1 + 4

Wiroko
fuente