Come confrontare le bandiere in C #?


155

Ho un enum di bandiera qui sotto.

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

Non posso fare in modo che l'istruzione if valuti come vera.

FlagTest testItem = FlagTest.Flag1 | FlagTest.Flag2;

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

Come posso renderlo vero?


Correggimi se sbaglio, è appropriato utilizzare 0 come valore flag?
Roy Lee,

4
@Roylee: 0 è accettabile ed è una buona idea avere un flag "None" o "Undefined" per provare senza avere flag impostati. Non è affatto richiesto, ma è una buona pratica. La cosa importante da ricordare al riguardo è sottolineata da Leonid nella sua risposta.
Andy,

5
@Roylee In realtà si consiglia a Microsoft di fornire un Noneflag con valore zero. Vedere msdn.microsoft.com/en-us/library/vstudio/...
ThatMatthew

Molte persone sostengono anche che il confronto dei bit è troppo difficile da leggere, quindi dovrebbe essere evitato a favore di una collezione di bandiere, dove puoi semplicemente fare collection.contains flag
MikeT

Tu eri abbastanza vicino, tranne che bisogna invertire la logica voi, è necessario il bit a bit &operatore per il confronto, |è come un'aggiunta: 1|2=3, 5|2=7, 3&2=2, 7&2=2, 8&2=0. 0valuta false, tutto il resto a true.
Damian Vogel,

Risposte:


321

In .NET 4 esiste un nuovo metodo Enum.HasFlag . Questo ti permette di scrivere:

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

che è molto più leggibile, IMO.

Il sorgente .NET indica che questo esegue la stessa logica della risposta accettata:

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

23
Ah, finalmente qualcosa fuori dagli schemi. È fantastico, sto aspettando questa funzionalità relativamente semplice da molto tempo. Sono contento che abbiano deciso di farlo entrare.
Rob van Groenewoud,

9
Nota, tuttavia, la risposta che segue mostra problemi di prestazioni con questo metodo: questo potrebbe essere un problema per alcune persone. Fortunatamente non per me.
Andy Mortimer,

2
La considerazione delle prestazioni per questo metodo è la boxe perché accetta argomenti come istanza della Enumclasse.
Adam Houldsworth il

1
Per informazioni sul problema delle prestazioni, guarda questa risposta: stackoverflow.com/q/7368652/200443
Maxence

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

(testItem & FlagTest.Flag1) è un'operazione AND bit a bit.

FlagTest.Flag1è equivalente a 001con l'enum di OP. Ora diciamo che testItemha Flag1 e Flag2 (quindi è bit a bit 101):

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

2
Qual è esattamente la logica qui? Perché il predicato deve essere scritto in questo modo?
Ian R. O'Brien,

4
@ IanR.O'Brien Flag1 | Flag 2 si traduce in 001 o 010, che è lo stesso di 011, ora se si fa uguaglianza di quello 011 == Flag1 o tradotto 011 == 001, restituisce sempre false. Ora se fai un AND bit a bit con Flag1, allora si traduce in 011 AND 001 che restituisce 001 ora facendo l'uguaglianza restituisce vero, perché 001 == 001
pqsk

È la soluzione migliore perché HasFlags richiede molte più risorse. E inoltre tutto ciò che HasFlags sta facendo, qui è fatto dal compilatore
Sebastian Xawery Wiśniowiecki,

78

Per coloro che hanno difficoltà a visualizzare ciò che sta accadendo con la soluzione accettata (che è questa),

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

testItem (come da domanda) è definito come,

testItem 
 = flag1 | flag2  
 = 001 | 010  
 = 011

Quindi, nell'istruzione if, il lato sinistro del confronto è,

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

E l'istruzione if completa (che restituisce true se flag1è impostata in testItem),

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

25

@ Phil-Devaney

Si noti che, tranne nel caso più semplice, Enum.HasFlag comporta una forte penalizzazione delle prestazioni rispetto alla scrittura manuale del codice. Considera il seguente codice:

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

Oltre 10 milioni di iterazioni, il metodo di estensione HasFlags richiede un enorme 4793 ms, rispetto ai 27 ms per l'implementazione bit a bit standard.


5
Infatti. Se guardi l'implementazione di HasFlag, noterai che esegue un "GetType ()" su entrambi gli operandi, il che è piuttosto lento. Quindi esegue "Enum.ToUInt64 (value.GetValue ());" su entrambi gli operandi prima di eseguire il controllo bit a bit.
user276648,

1
Ho eseguito il test più volte e ho ricevuto ~ 500ms per HasFlags e ~ 32ms per bit a bit. Mentre è ancora un ordine di grandezza più veloce con bit a bit, HasFlags era un ordine di grandezza giù dal test. (Ha eseguito il test su un Core i3 a 2,5 GHz e .NET 4.5)
MarioVW,

1
@MarioVW In esecuzione più volte su .NET 4, i7-3770 offre ~ 2400ms vs ~ 20ms in modalità AnyCPU (64-bit) e ~ 3000ms vs ~ 20ms in modalità 32-bit. .NET 4.5 potrebbe averlo leggermente ottimizzato. Si noti inoltre la differenza di prestazioni tra build a 64 e 32 bit, che potrebbe essere dovuta a un'aritmetica a 64 bit più veloce (vedere il primo commento).
Bob

1
(«flag var» & «flag value») != 0non funziona per me. La condizione fallisce sempre e il mio compilatore (Unity3D's Mono 2.6.5) riporta un "avviso CS0162: codice irraggiungibile rilevato" quando utilizzato in un if (…).
Slipp D. Thompson,

1
@ wraith808: mi sono reso conto che l'errore stava facendo con il mio test che hai corretto nel tuo: la potenza di 2 … = 1, … = 2, … = 4sui valori di enum è di fondamentale importanza durante l'utilizzo [Flags]. Stavo supponendo che avrebbe iniziato le voci 1e l'avanzato automaticamente di Po2s. Il comportamento sembra coerente tra MS .NET e Mono di Unity. Mi scuso per questo.
Slipp D. Thompson,

21

Ho impostato un metodo di estensione per farlo: domanda correlata .

Fondamentalmente:

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

Quindi puoi fare:

FlagTests testItem = FlagTests.Flag1 | FlagTests.Flag2;

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

Per inciso, la convenzione che uso per gli enum è singolare per standard, plurale per flag. In questo modo sai dal nome dell'enum se può contenere più valori.


Questo dovrebbe essere un commento, ma dato che sono un nuovo utente sembra che non riesca ancora ad aggiungere commenti ... bool pubblico statico IsSet (questo input Enum, Enum matchTo) {return (Convert.ToUInt32 (input) & Convert .ToUInt32 (matchTo))! = 0; } C'è un modo per essere compatibile con qualsiasi tipo di enum (perché qui non funzionerà se il tuo enum è di tipo UInt64 o può avere valori negativi)?
user276648,

Questo è abbastanza ridondante con Enum.HasFlag (Enum) (disponibile in .net 4.0)
PPC

1
@PPC Non direi esattamente ridondante: molte persone stanno sviluppando versioni precedenti del framework. Hai ragione però, gli utenti .Net 4 dovrebbero usare l' HasFlagestensione.
Keith,

4
@Keith: Inoltre, c'è una notevole differenza: ((FlagTest) 0x1) .HasFlag (0x0) restituirà true, il che potrebbe o meno essere un comportamento desiderato
PPC

19

Un altro consiglio ... Non eseguire mai il controllo binario standard con la bandiera il cui valore è "0". Il tuo controllo su questa bandiera sarà sempre vero.

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

Se si controlla binariamente il parametro di input su FullInfo, si ottiene:

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

bPRez sarà sempre vero come QUALCOSA e 0 sempre == 0.


Invece dovresti semplicemente controllare che il valore dell'input sia 0:

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

Ho appena corretto un bug del tipo 0 flag. Penso che questo sia un errore di progettazione in .NET framework (3.5) perché è necessario sapere quale dei valori di flag è 0 prima di testarlo.
thersch

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

5

Per operazioni a bit, è necessario utilizzare operatori bit a bit.

Questo dovrebbe fare il trucco:

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

Modifica: risolto il mio if check - Sono tornato nei miei modi C / C ++ (grazie a Ryan Farley per averlo sottolineato)


5

Per quanto riguarda la modifica. Non puoi renderlo vero. Ti suggerisco di racchiudere ciò che vuoi in un'altra classe (o metodo di estensione) per avvicinarti alla sintassi di cui hai bisogno.

vale a dire

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

4

Prova questo:


if ((testItem & FlagTest.Flag1) == FlagTest.Flag1)
{
    // do something
}
Fondamentalmente, il tuo codice ti chiede se avere entrambi i flag impostati è lo stesso di avere un flag impostato, il che è ovviamente falso. Il codice sopra lascerà impostato solo il bit Flag1 se è impostato, quindi confronta questo risultato con Flag1.


1

anche senza [Flags], potresti usare qualcosa del genere

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

o se hai un enum di valore Zero

if((testItem & (FlagTest.Flag1 | FlagTest.Flag2 ))!=FlagTest.None){
//..
}
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.