Wie vergleiche ich Flags in C #?

155

Ich habe unten eine Flaggenaufzählung.

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

Ich kann die if-Anweisung nicht als wahr bewerten lassen.

FlagTest testItem = FlagTest.Flag1 | FlagTest.Flag2;

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

Wie kann ich das wahr machen?

David Basarab
quelle
Korrigieren Sie mich, wenn ich falsch liege. Ist 0 geeignet, um als Flag-Wert verwendet zu werden?
Roy Lee
4
@ Roylee: 0 ist akzeptabel und es ist eine gute Idee, ein "None" - oder "Undefined" -Flag zu haben, um zu testen, ob keine Flags gesetzt sind. Es ist keineswegs erforderlich, aber es ist eine gute Praxis. Das Wichtige daran ist Leonid in seiner Antwort hervorzuheben.
Andy
5
@ Roylee Es wird von Microsoft empfohlen, ein NoneFlag mit dem Wert Null anzugeben . Siehe msdn.microsoft.com/en-us/library/vstudio/…
ThatMatthew
Viele Leute argumentieren auch, dass der Bitvergleich zu schwer zu lesen ist und daher zugunsten einer Sammlung von Flaggen vermieden werden sollte, bei denen Sie nur die Sammlung durchführen können.
Enthält
Sie waren ganz in der Nähe, außer dass Sie Ihnen Logik umkehren müssen, müssen Sie den bitweise &Operator zum Vergleich, |ist wie ein Zusatz: 1|2=3, 5|2=7, 3&2=2, 7&2=2, 8&2=0. 0bewertet zu false, alles andere zu true.
Damian Vogel

Antworten:

321

In .NET 4 gibt es eine neue Methode Enum.HasFlag . So können Sie schreiben:

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

Das ist viel besser lesbar, IMO.

Die .NET-Quelle gibt an, dass dies dieselbe Logik wie die akzeptierte Antwort ausführt:

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
quelle
23
Ah, endlich etwas aus der Box. Das ist großartig, ich habe lange auf diese relativ einfache Funktion gewartet. Ich bin froh, dass sie beschlossen haben, es einzuschieben.
Rob van Groenewoud
9
Beachten Sie jedoch, dass die folgende Antwort Leistungsprobleme mit dieser Methode zeigt - dies kann für einige Personen ein Problem sein. Zum Glück nicht für mich.
Andy Mortimer
2
Die Leistungsüberlegung für diese Methode ist das Boxen, da Argumente als Instanz der EnumKlasse verwendet werden.
Adam Houldsworth
1
Informationen zum Leistungsproblem finden Sie in dieser Antwort: stackoverflow.com/q/7368652/200443
Maxence
180
if ((testItem & FlagTest.Flag1) == FlagTest.Flag1)
{
     // Do something
}

(testItem & FlagTest.Flag1) ist eine bitweise UND-Verknüpfung.

FlagTest.Flag1entspricht 001der Aufzählung von OP. Lassen Sie uns jetzt sagen testItemhat Flag1 und Flag2 (so ist es bitweise 101):

  001
 &101
 ----
  001 == FlagTest.Flag1
Scott Nichols
quelle
2
Was genau ist die Logik hier? Warum muss das Prädikat so geschrieben werden?
Ian R. O'Brien
4
@ IanR.O'Brien Flag1 | Flag 2 wird in 001 oder 010 übersetzt, was mit 011 identisch ist. Wenn Sie nun eine Gleichheit von 011 == Flag1 oder 011 == 001 übersetzen, wird immer false zurückgegeben. Wenn Sie nun ein bitweises UND mit Flag1 ausführen, wird es in 011 UND 001 übersetzt, was 001 zurückgibt, wobei die Gleichheit true zurückgibt, da 001 == 001
pqsk
Dies ist die beste Lösung, da HasFlags viel ressourcenintensiver ist. Und außerdem wird alles, was HasFlags tut, vom Compiler erledigt
Sebastian Xawery Wiśniowiecki
78

Für diejenigen, die Probleme haben, sich vorzustellen, was mit der akzeptierten Lösung passiert (was das ist),

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

testItem (gemäß der Frage) ist definiert als,

testItem 
 = flag1 | flag2  
 = 001 | 010  
 = 011

Dann ist in der if-Anweisung die linke Seite des Vergleichs:

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

Und die vollständige if-Anweisung (die als true ausgewertet wird, wenn gesetzt flag1ist testItem),

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

@ Phil-Devaney

Beachten Sie, dass Enum.HasFlag , außer in den einfachsten Fällen, im Vergleich zum manuellen Schreiben des Codes eine erhebliche Leistungsminderung mit sich bringt. Betrachten Sie den folgenden Code:

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

Über 10 Millionen Iterationen benötigt die HasFlags-Erweiterungsmethode satte 4793 ms, verglichen mit 27 ms für die standardmäßige bitweise Implementierung.

Chuck Dee
quelle
5
Tatsächlich. Wenn Sie sich die Implementierung von HasFlag ansehen, werden Sie feststellen, dass auf beiden Operanden ein "GetType ()" ausgeführt wird, was ziemlich langsam ist. Dann wird "Enum.ToUInt64 (value.GetValue ())" ausgeführt. auf beiden Operanden vor der bitweisen Prüfung.
user276648
1
Ich habe Ihren Test mehrmals durchgeführt und ~ 500 ms für HasFlags und ~ 32 ms für bitweise erhalten. HasFlags war zwar bitweise immer noch eine Größenordnung schneller, aber eine Größenordnung niedriger als Ihr Test. (Lief den Test auf einem 2,5 GHz Core i3 und .NET 4.5)
MarioVW
1
@MarioVW i7-3770 wird unter .NET 4 mehrmals ausgeführt und bietet im AnyCPU-Modus (64-Bit) ~ 2400 ms gegenüber ~ 20 ms und im 32-Bit-Modus ~ 3000 ms gegenüber ~ 20 ms. .NET 4.5 hat es möglicherweise leicht optimiert. Beachten Sie auch den Leistungsunterschied zwischen 64-Bit- und 32-Bit-Builds, der möglicherweise auf eine schnellere 64-Bit-Arithmetik zurückzuführen ist (siehe ersten Kommentar).
Bob
1
(«flag var» & «flag value») != 0funktioniert bei mir nicht Die Bedingung schlägt immer fehl und mein Compiler (Mono 2.6.5 von Unity3D) meldet eine "Warnung CS0162: Nicht erreichbarer Code erkannt", wenn er in einem verwendet wird if (…).
Slipp D. Thompson
1
@ wraith808: Ich habe festgestellt, dass der Fehler bei meinem Test gemacht wurde, den Sie in Ihrem richtig gemacht haben - die Potenz von 2 … = 1, … = 2, … = 4auf den Enum-Werten ist bei der Verwendung von entscheidender Bedeutung [Flags]. Ich ging davon aus, dass es die Einträge bei 1Po2s automatisch starten und von Po2s vorrücken würde. Das Verhalten sieht in MS .NET und Unitys datiertem Mono konsistent aus. Ich entschuldige mich dafür.
Slipp D. Thompson
21

Ich habe dafür eine Erweiterungsmethode eingerichtet: verwandte Frage .

Grundsätzlich:

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

Dann können Sie tun:

FlagTests testItem = FlagTests.Flag1 | FlagTests.Flag2;

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

Übrigens ist die Konvention, die ich für Enums verwende, Singular für Standard, Plural für Flags. Auf diese Weise wissen Sie anhand des Aufzählungsnamens, ob er mehrere Werte enthalten kann.

Keith
quelle
Dies sollte ein Kommentar sein, aber da ich ein neuer Benutzer bin, kann ich anscheinend noch keine Kommentare hinzufügen ... public static bool IsSet (diese Enum-Eingabe, Enum matchTo) {return (Convert.ToUInt32 (Eingabe) & Convert .ToUInt32 (matchTo))! = 0; } Gibt es eine Möglichkeit, mit jeder Art von Aufzählung kompatibel zu sein (da dies hier nicht funktioniert, wenn Ihre Aufzählung vom Typ UInt64 ist oder negative Werte haben kann)?
user276648
Dies ist ziemlich redundant mit Enum.HasFlag (Enum) (verfügbar in .net 4.0)
PPC
1
@PPC Ich würde nicht genau sagen, redundant - viele Leute entwickeln ältere Versionen des Frameworks. Sie haben jedoch Recht. Net 4-Benutzer sollten HasFlagstattdessen die Erweiterung verwenden.
Keith
4
@ Keith: Auch gibt es einen bemerkenswerten Unterschied: ((FlagTest) 0x1) .HasFlag (0x0) wird true zurückgeben, was ein gewünschtes Verhalten sein kann oder nicht
PPC
19

Noch ein Ratschlag ... Führen Sie niemals die Standard-Binärprüfung mit dem Flag durch, dessen Wert "0" ist. Ihre Überprüfung dieser Flagge wird immer wahr sein.

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

Wenn Sie den Eingabeparameter binär gegen FullInfo prüfen, erhalten Sie:

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

bPRez ist immer wahr als ALLES & 0 immer == 0.


Stattdessen sollten Sie einfach überprüfen, ob der Wert der Eingabe 0 ist:

bool bPRez = (detailLevel == LevelOfDetail.FullInfo);
Leonid
quelle
Ich habe gerade einen solchen 0-Flag-Fehler behoben. Ich denke, dies ist ein Entwurfsfehler in .NET Framework (3.5), da Sie wissen müssen, welcher der Flag-Werte 0 ist, bevor Sie ihn testen.
Thersch
7
if((testItem & FlagTest.Flag1) == FlagTest.Flag1) 
{
...
}
Damian
quelle
5

Für Bitoperationen müssen Sie bitweise Operatoren verwenden.

Dies sollte den Trick tun:

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

Bearbeiten: Meine if-Prüfung wurde behoben. Ich bin wieder in meine C / C ++ - Methoden zurückgekehrt (danke an Ryan Farley für den Hinweis).

17 von 26
quelle
5

In Bezug auf die Bearbeitung. Du kannst es nicht wahr machen. Ich schlage vor, dass Sie das, was Sie wollen, in eine andere Klasse (oder Erweiterungsmethode) einbinden, um der von Ihnen benötigten Syntax näher zu kommen.

dh

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

Versuche dies:


if ((testItem & FlagTest.Flag1) == FlagTest.Flag1)
{
    // do something
}
Grundsätzlich fragt Ihr Code, ob das Setzen beider Flags mit dem Setzen eines Flags identisch ist, was offensichtlich falsch ist. Der obige Code lässt nur das Flag1-Bit gesetzt, wenn es überhaupt gesetzt ist, und vergleicht dieses Ergebnis dann mit Flag1.

OwenP
quelle
1

Auch ohne [Flags] könnten Sie so etwas verwenden

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

oder wenn Sie eine Nullwert-Aufzählung haben

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