2008-09-02 20 views
133

Tengo una enumeración de la bandera a continuación.¿Cómo comparar las banderas en C#?

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

No puedo hacer que la sentencia 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 cierto?

+0

corrígeme si me equivoco, es apropiada para ser 0 utilizado como valor de la bandera? – Roylee

+3

@Roylee: 0 es aceptable, y es una buena idea tener una bandera "Ninguno" o "Indefinido" para probar que no se han establecido banderas. De ninguna manera es obligatorio, pero es una buena práctica. Lo importante de recordar sobre esto es señalado por Leonid en su respuesta. – Andy

+4

@Roylee En realidad, Microsoft recomienda que proporcione un indicador de "Ninguno" con un valor de cero. Consulte http://msdn.microsoft.com/en-us/library/vstudio/ms229058(v=vs.100).aspx – ThatMatthew

Respuesta

289

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

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

que es mucho más legible, IMO.

El.fuente neta indica que este 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); 
} 
+22

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 decidieron deslizarlo. –

+8

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. Afortunadamente no para mí. –

+1

La consideración de rendimiento para este método es boxeo porque toma argumentos como una instancia de la clase 'Enum'. –

4

Para operaciones de bits, necesita utilizar operadores bit a bit.

Esto debería hacer el truco:

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

Editar: fijo mi si cheque - Me puse de nuevo en mi C/C++ maneras (gracias a Ryan Farley para señalarlo)

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

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

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

001 
&101 
---- 
    001 == FlagTest.Flag1 
+2

¿Cuál es exactamente la lógica aquí? ¿Por qué el predicado debe escribirse así? –

+3

@ IanR.O'Brien Flag1 | La bandera 2 traduce a 001 o 010 que es lo mismo que 011, ahora si haces una igualdad de ese 011 == Flag1 o tradujo 011 == 001, eso devuelve falso siempre. Ahora si haces un Y a nivel de bit con Flag1, entonces se traduce a 011 Y 001 que devuelve 001 ahora haciendo trueres de igualdad true, porque 001 == 001 – pqsk

+0

Es la mejor solución porque HasFlags consume mucho más recursos. Y además, todo lo que HasFlags está haciendo, aquí lo hace el compilador –

7
if((testItem & FlagTest.Flag1) == FlagTest.Flag1) 
{ 
... 
} 
4

Prueba esto:


if ((testItem & FlagTest.Flag1) == FlagTest.Flag1) 
{ 
    // do something 
} 
Básicamente, el código está preguntando si tienen ambos indicadores establecidos es lo mismo que tener un conjunto bandera, que obviamente es falso El código de arriba dejará solo el bit de Flag1 establecido si está configurado en absoluto, luego compara este resultado con Flag1.

21

Configuré un método de extensión para hacerlo: related question.

Básicamente:

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

entonces usted puede hacer:

FlagTests testItem = FlagTests.Flag1 | FlagTests.Flag2; 

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

A propósito de la convención que utilizo para enumeraciones es singular para el estándar, plural para banderas. De esta forma sabrá a partir del nombre enum si puede contener múltiples valores.

+0

Esto debería ser un comentario, pero como soy un nuevo usuario parece que aún no puedo agregar comentarios ... pública bool isset estática (esta entrada Enum, Enum matchTo) { retorno (Convert.ToUInt32 (entrada) y Convert.ToUInt32 (matchTo)) = 0; } ¿Hay una manera que sea compatible con cualquier tipo de enumeración (porque aquí no va a funcionar si su enumeración es de tipo UInt64 o puede tener valores negativos)? – user276648

+0

Esto es bastante redundante con Enum.HasFlag (Enum) (disponible en .net 4.0) – PPC

+1

@PPC No diría redundante exactamente: muchas personas están desarrollando versiones anteriores del framework. Sin embargo, tienes razón. Los usuarios de .Net 4 deberían usar la extensión 'HasFlag' en su lugar. – Keith

3

En cuanto a la edición. No puedes hacer que sea verdad. Te sugiero que envuelvas lo que quieras en otra clase (o método de extensión) para acercarte a la sintaxis que necesitas.

es decir

public class FlagTestCompare 
{ 
    public static bool Compare(this FlagTest myFlag, FlagTest condition) 
    { 
     return ((myFlag & condition) == condition); 
    } 
} 
77

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 (de acuerdo con la pregunta) se define como,

testItem 
= flag1 | flag2 
= 001 | 010 
= 011 

a continuación, en la sentencia if, el lado izquierdo de la comparación es,

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

y la plena instrucción if (que es verdadero si flag1 se encuentra en testItem),

(testItem & flag1) == flag1 
= (001) == 001 
= true 
19

Un consejo más ... Nunca hacer la comprobación binaria estándar con la bandera cuya el valor es "0". Su cheque en esta bandera siempre será verdadero.

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

Si parámetro de entrada binaria cheque contra FullInfo - se obtiene:

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

bPRez siempre será cierto como cualquier cosa & 0 siempre == 0.


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

bool bPRez = (detailLevel == LevelOfDetail.FullInfo); 
+0

Acabo de corregir un error de 0-flag. Creo que esto es un error de diseño en .NET Framework (3.5) porque necesita saber cuál de los valores de indicador es 0 antes de probarlo. – thersch

21

@ Phil-devaney

Tenga en cuenta que, excepto en los casos más sencillos, la Enum.HasFlag lleva una penalización de rendimiento pesada en comparación con escribir el código manualmente. 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 lleva la friolera de 4793 ms, en comparación con los 27 ms para la puesta en práctica a nivel de bits estándar.

+5

De hecho. Si observas la implementación de HasFlag, verás que tiene 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

+0

Esta es una gran diferencia de rendimiento para una ganancia de funcionalidad muy pequeña. Vale la pena dejar de usar HasFlag() – PPC

+1

He realizado varias pruebas y obtuve ~ 500ms para HasFlags y ~ 32ms para bitwise. Si bien sigue siendo un orden de magnitud más rápido con el bit a bit, HasFlags fue un orden de magnitud inferior a la prueba. (Corrió la prueba en un Core i3 de 2.5GHz y .NET 4.5) – MarioVW

1

incluso sin [Banderas], podría utilizar algo como esto

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

o si tiene una enumeración valor cero

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