¿Cómo comparar banderas en C #?

155

Tengo una enumeración de bandera a continuación.

[Flags]
public enum FlagTest
{
    None = 0x0,
    Flag1 = 0x1,
    Flag2 = 0x2,
    Flag3 = 0x4
}

No puedo hacer que la declaración if se evalúe como verdadera.

FlagTest testItem = FlagTest.Flag1 | FlagTest.Flag2;

if (testItem == FlagTest.Flag1)
{
    // Do something,
    // however This is never true.
}

¿Cómo puedo hacer esto verdad?

David Basarab
fuente
Corríjame si me equivoco, ¿es 0 apropiado para ser utilizado como valor de bandera?
Roy Lee
44
@Roylee: 0 es aceptable, y es una buena idea tener un indicador "Ninguno" o "Indefinido" para probar que no se hayan establecido indicadores. De ninguna manera es obligatorio, pero es una buena práctica. Lo importante para recordar sobre esto lo señala Leonid en su respuesta.
Andy
55
@Roylee Microsoft recomienda realmente que proporcione un Noneindicador con un valor de cero. Ver msdn.microsoft.com/en-us/library/vstudio/…
ThatMatthew
Mucha gente también argumenta que la comparación de bits es demasiado difícil de leer, por lo que debe evitarse en favor de una colección de banderas, donde solo puede hacer la colección
Contiene la
Usted estaba bastante cerca, excepto que usted tiene que invertir la lógica, es necesario el bit a bit &operador de comparación, |es como una adición: 1|2=3, 5|2=7, 3&2=2, 7&2=2, 8&2=0. 0evalúa a false, todo lo demás a true.
Damian Vogel

Respuestas:

321

En .NET 4 hay un nuevo método Enum.HasFlag . Esto te permite escribir:

if ( testItem.HasFlag( FlagTest.Flag1 ) )
{
    // Do Stuff
}

que es mucho más legible, en mi opinión.

La fuente .NET indica que esto realiza la misma lógica que la respuesta aceptada:

public Boolean HasFlag(Enum flag) {
    if (!this.GetType().IsEquivalentTo(flag.GetType())) {
        throw new ArgumentException(
            Environment.GetResourceString(
                "Argument_EnumTypeDoesNotMatch", 
                flag.GetType(), 
                this.GetType()));
    }

    ulong uFlag = ToUInt64(flag.GetValue()); 
    ulong uThis = ToUInt64(GetValue());
    // test predicate
    return ((uThis & uFlag) == uFlag); 
}
Phil Devaney
fuente
23
Ah, finalmente algo fuera de la caja. Esto es genial, he estado esperando esta característica relativamente simple durante mucho tiempo. Me alegro de que decidieran introducirlo.
Rob van Groenewoud
9
Sin embargo, tenga en cuenta que la respuesta a continuación muestra problemas de rendimiento con este método; esto podría ser un problema para algunas personas. Felizmente no para mi.
Andy Mortimer
2
La consideración de rendimiento para este método es el boxeo porque toma argumentos como una instancia de la Enumclase.
Adam Houldsworth
1
Para obtener información sobre el problema de rendimiento, mire esta respuesta: stackoverflow.com/q/7368652/200443
Maxence
180
if ((testItem & FlagTest.Flag1) == FlagTest.Flag1)
{
     // Do something
}

(testItem & FlagTest.Flag1) es una operación AND a nivel de bit.

FlagTest.Flag1es equivalente a 001con la enumeración de OP. Ahora digamos que testItemtiene Flag1 y Flag2 (por lo que es bit a bit 101):

  001
 &101
 ----
  001 == FlagTest.Flag1
Scott Nichols
fuente
2
¿Cuál es exactamente la lógica aquí? ¿Por qué el predicado tiene que escribirse así?
Ian R. O'Brien
44
@ IanR.O'Brien Flag1 | La bandera 2 se traduce a 001 o 010, que es lo mismo que 011, ahora si haces una igualdad de esa 011 == Bandera1 o traducida 011 == 001, eso devuelve falso siempre. Ahora, si haces un AND bit a bit con Flag1, entonces se traduce a 011 AND 001, que devuelve 001, ahora haciendo que la igualdad devuelva verdadero, porque 001 == 001
pqsk
Es la mejor solución porque HasFlags requiere muchos más recursos. Y además, todo lo que está haciendo HasFlags, aquí lo hace el compilador
Sebastian Xawery Wiśniowiecki,
78

Para aquellos que tienen problemas para visualizar lo que está sucediendo con la solución aceptada (que es esto),

if ((testItem & FlagTest.Flag1) == FlagTest.Flag1)
{
    // Do stuff.
}

testItem (según la pregunta) se define como,

testItem 
 = flag1 | flag2  
 = 001 | 010  
 = 011

Luego, en la declaración if, el lado izquierdo de la comparación es,

(testItem & flag1) 
 = (011 & 001) 
 = 001

Y la declaración if completa (que se evalúa como verdadera si flag1se establece en testItem),

(testItem & flag1) == flag1
 = (001) == 001
 = true
Sekhat
fuente
25

@ phil-devaney

Tenga en cuenta que, excepto en los casos más simples, Enum.HasFlag conlleva una fuerte penalización de rendimiento en comparación con la escritura manual del código. Considere el siguiente código:

[Flags]
public enum TestFlags
{
    One = 1,
    Two = 2,
    Three = 4,
    Four = 8,
    Five = 16,
    Six = 32,
    Seven = 64,
    Eight = 128,
    Nine = 256,
    Ten = 512
}


class Program
{
    static void Main(string[] args)
    {
        TestFlags f = TestFlags.Five; /* or any other enum */
        bool result = false;

        Stopwatch s = Stopwatch.StartNew();
        for (int i = 0; i < 10000000; i++)
        {
            result |= f.HasFlag(TestFlags.Three);
        }
        s.Stop();
        Console.WriteLine(s.ElapsedMilliseconds); // *4793 ms*

        s.Restart();
        for (int i = 0; i < 10000000; i++)
        {
            result |= (f & TestFlags.Three) != 0;
        }
        s.Stop();
        Console.WriteLine(s.ElapsedMilliseconds); // *27 ms*        

        Console.ReadLine();
    }
}

Más de 10 millones de iteraciones, el método de extensión HasFlags toma la friolera de 4793 ms, en comparación con los 27 ms para la implementación bit a bit estándar.

Chuck Dee
fuente
55
En efecto. Si observa la implementación de HasFlag, verá que hace un "GetType ()" en ambos operandos, que es bastante lento. Luego hace "Enum.ToUInt64 (value.GetValue ());" en ambos operandos antes de hacer la comprobación bit a bit.
user276648
1
Ejecuté su prueba varias veces y obtuve ~ 500ms para HasFlags y ~ 32ms para bit a bit. Aunque todavía era un orden de magnitud más rápido con bit a bit, HasFlags fue un orden de magnitud inferior a su prueba. (Ejecuté la prueba en un Core i3 de 2.5GHz y .NET 4.5)
MarioVW
1
@MarioVW Al ejecutarse varias veces en .NET 4, i7-3770 ofrece ~ 2400 ms frente a ~ 20 ms en modo AnyCPU (64 bits) y ~ 3000 ms frente a ~ 20 ms en modo 32 bits. .NET 4.5 puede haberlo optimizado ligeramente. También tenga en cuenta la diferencia de rendimiento entre las compilaciones de 64 bits y 32 bits, que podría deberse a una aritmética de 64 bits más rápida (vea el primer comentario).
Bob
1
(«flag var» & «flag value») != 0no funciona para mi La condición siempre falla y mi compilador (Unity3D's Mono 2.6.5) informa una "advertencia CS0162: Código inalcanzable detectado" cuando se usa en un if (…).
Slipp D. Thompson
1
@ wraith808: Me di cuenta de que el error se estaba cometiendo con mi prueba de que tienes lo correcto en la tuya; el poder de 2 … = 1, … = 2, … = 4en los valores de enumeración es de importancia crítica cuando se usa [Flags]. 1Supuse que comenzaría las entradas en y avanzaría por Po2s automáticamente. El comportamiento se ve consistente en MS .NET y Mono con fecha de Unity. Mis disculpas por poner esto en ti.
Slipp D. Thompson
21

Configuré un método de extensión para hacerlo: pregunta relacionada .

Básicamente:

public static bool IsSet( this Enum input, Enum matchTo )
{
    return ( Convert.ToUInt32( input ) & Convert.ToUInt32( matchTo ) ) != 0;
}

Entonces puedes hacer:

FlagTests testItem = FlagTests.Flag1 | FlagTests.Flag2;

if( testItem.IsSet ( FlagTests.Flag1 ) )
    //Flag1 is set

Por cierto, la convención que uso para las enumeraciones es singular para estándar, plural para banderas. De esa manera, usted sabe por el nombre de enumeración si puede contener múltiples valores.

Keith
fuente
Esto debería ser un comentario, pero como soy un usuario nuevo, parece que todavía no puedo agregar comentarios ... public static bool IsSet (this Enum input, Enum matchTo) {return (Convert.ToUInt32 (input) & Convert .ToUInt32 (matchTo))! = 0; } ¿Hay alguna manera de ser compatible con cualquier tipo de enumeración (porque aquí no funcionará si su enumeración es del tipo UInt64 o puede tener valores negativos)?
user276648
Esto es bastante redundante con Enum.HasFlag (Enum) (disponible en .net 4.0)
PPC
1
@PPC No diría que es redundante exactamente: mucha gente está desarrollando versiones anteriores del marco. Sin embargo, tiene razón, los usuarios de .Net 4 deberían usar la HasFlagextensión.
Keith
44
@Keith: Además, hay una diferencia notable: ((FlagTest) 0x1). HasFlag (0x0) devolverá verdadero, lo que puede o no ser un comportamiento deseado
PPC
19

Un consejo más ... Nunca haga la verificación binaria estándar con la bandera cuyo valor es "0". Su verificación en esta bandera siempre será verdadera.

[Flags]
public enum LevelOfDetail
{
    [EnumMember(Value = "FullInfo")]
    FullInfo=0,
    [EnumMember(Value = "BusinessData")]
    BusinessData=1
}

Si comprueba binariamente el parámetro de entrada con FullInfo, obtendrá:

detailLevel = LevelOfDetail.BusinessData;
bool bPRez = (detailLevel & LevelOfDetail.FullInfo) == LevelOfDetail.FullInfo;

bPRez siempre será verdadero como CUALQUIER COSA & 0 siempre == 0.


En su lugar, simplemente debe verificar que el valor de la entrada es 0:

bool bPRez = (detailLevel == LevelOfDetail.FullInfo);
Leonid
fuente
Acabo de arreglar un error de bandera 0. Creo que este es un error de diseño en .NET Framework (3.5) porque necesita saber cuál de los valores del indicador es 0 antes de probarlo.
thersch
7
if((testItem & FlagTest.Flag1) == FlagTest.Flag1) 
{
...
}
Damian
fuente
5

Para las operaciones con bits, debe usar operadores bit a bit.

Esto debería funcionar:

if ((testItem & FlagTest.Flag1) == FlagTest.Flag1)
{
    // Do something,
    // however This is never true.
}

Editar: se corrigió mi verificación if: volví a mis formas C / C ++ (gracias a Ryan Farley por señalarlo)

17 de 26
fuente
5

En cuanto a la edición. No puedes hacerlo realidad. Le sugiero que ajuste lo que desea en otra clase (o método de extensión) para acercarse a la sintaxis que necesita.

es decir

public class FlagTestCompare
{
    public static bool Compare(this FlagTest myFlag, FlagTest condition)
    {
         return ((myFlag & condition) == condition);
    }
}
Martin Clarke
fuente
4

Prueba esto:


if ((testItem & FlagTest.Flag1) == FlagTest.Flag1)
{
    // do something
}
Básicamente, su código le pregunta si tener ambas banderas establecidas es lo mismo que tener una bandera establecida, lo que obviamente es falso. El código anterior dejará solo el bit Flag1 establecido si está configurado, luego compara este resultado con Flag1.

OwenP
fuente
1

incluso sin [Banderas], podrías usar algo como esto

if((testItem & (FlagTest.Flag1 | FlagTest.Flag2 ))!=0){
//..
}

o si tienes una enumeración de valor cero

if((testItem & (FlagTest.Flag1 | FlagTest.Flag2 ))!=FlagTest.None){
//..
}
Waleed AK
fuente