Comment comparer les drapeaux en C #?


155

J'ai une énumération de drapeau ci-dessous.

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

Je ne peux pas donner à l'instruction if la valeur true.

FlagTest testItem = FlagTest.Flag1 | FlagTest.Flag2;

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

Comment puis-je rendre cela vrai?


Corrigez-moi si je me trompe, est-ce que 0 est approprié pour être utilisé comme valeur d'indicateur?
Roy Lee

4
@Roylee: 0 est acceptable, et c'est une bonne idée d'avoir un drapeau "Aucun" ou "Non défini" afin de tester l'absence de fanion. Ce n'est en aucun cas obligatoire, mais c'est une bonne pratique. La chose importante à retenir à ce sujet est soulignée par Leonid dans sa réponse.
Andy

5
@Roylee Il est en fait recommandé par Microsoft de fournir un Noneindicateur avec une valeur de zéro. Voir msdn.microsoft.com/en-us/library/vstudio/…
ThatMatthew

Beaucoup de gens affirment également que la comparaison de bits est trop difficile à lire, il faut donc éviter une collection de drapeaux, où vous pouvez simplement faire collection.contains flag
MikeT

Vous étiez très proche, sauf que vous devez vous inverser la logique, vous devez l'opérateur de bits &opérateur à titre de comparaison, |est comme une addition: 1|2=3, 5|2=7, 3&2=2, 7&2=2, 8&2=0. 0évalue à false, tout le reste à true.
Damian Vogel

Réponses:


321

Dans .NET 4, il existe une nouvelle méthode Enum.HasFlag . Cela vous permet d'écrire:

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

ce qui est beaucoup plus lisible, IMO.

La source .NET indique que cela exécute la même logique que la réponse acceptée:

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, enfin quelque chose hors de la boîte. C'est génial, j'attendais cette fonctionnalité relativement simple depuis longtemps. Heureux qu'ils aient décidé de le glisser.
Rob van Groenewoud

9
Notez, cependant, la réponse ci-dessous indiquant les problèmes de performances avec cette méthode - cela peut être un problème pour certaines personnes. Heureusement pas pour moi.
Andy Mortimer

2
La considération de performance pour cette méthode est la boxe car elle prend des arguments comme une instance de la Enumclasse.
Adam Houldsworth

1
Pour plus d'informations sur le problème de performances, regardez cette réponse: stackoverflow.com/q/7368652/200443
Maxence

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

(testItem & FlagTest.Flag1) est une opération AND au niveau du bit.

FlagTest.Flag1équivaut à 001l'énumération de OP. Maintenant, disons testItema Flag1 et Flag2 (donc c'est au niveau du bit 101):

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

2
Quelle est exactement la logique ici? Pourquoi le prédicat doit-il être écrit ainsi?
Ian R. O'Brien

4
@ IanR.O'Brien Flag1 | L'indicateur 2 se traduit par 001 ou 010 qui est identique à 011, maintenant si vous faites une égalité de ce 011 == Flag1 ou traduit 011 == 001, cela renvoie toujours faux. Maintenant, si vous faites un ET au niveau du bit avec Flag1, cela se traduit par 011 AND 001 qui retourne 001 maintenant, l'égalité retourne true, car 001 == 001
pqsk

C'est la meilleure solution car HasFlags est beaucoup plus gourmand en ressources. Et de plus, tout ce que fait HasFlags, ici est fait par compilateur
Sebastian Xawery Wiśniowiecki

78

Pour ceux qui ont du mal à visualiser ce qui se passe avec la solution acceptée (qui est la suivante),

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

testItem (selon la question) est défini comme,

testItem 
 = flag1 | flag2  
 = 001 | 010  
 = 011

Ensuite, dans l'instruction if, le côté gauche de la comparaison est,

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

Et l'instruction if complète (qui prend la valeur true si flag1est définie dans testItem),

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

25

@ phil-devaney

Notez que, sauf dans les cas les plus simples, Enum.HasFlag entraîne une forte pénalité de performances par rapport à l'écriture manuelle du code. Considérez le code suivant:

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

Plus de 10 millions d'itérations, la méthode d'extension HasFlags prend 4793 ms, contre 27 ms pour l'implémentation standard au niveau du bit.


5
En effet. Si vous regardez l'implémentation de HasFlag, vous verrez qu'il fait un "GetType ()" sur les deux opérandes, ce qui est assez lent. Ensuite, il fait "Enum.ToUInt64 (value.GetValue ());" sur les deux opérandes avant d'effectuer la vérification au niveau du bit.
user276648

1
J'ai exécuté votre test plusieurs fois et j'ai obtenu ~ 500ms pour HasFlags et ~ 32ms pour bitwise. Bien qu'il soit toujours un ordre de grandeur plus rapide avec le bit, HasFlags était un ordre de grandeur inférieur à votre test. (A effectué le test sur un Core i3 à 2,5 GHz et .NET 4.5)
MarioVW

1
@MarioVW Exécuté plusieurs fois sur .NET 4, i7-3770 donne ~ 2400 ms contre ~ 20 ms en mode AnyCPU (64 bits) et ~ 3000 ms contre ~ 20 ms en mode 32 bits. .NET 4.5 l'a peut-être légèrement optimisé. Notez également la différence de performances entre les versions 64 bits et 32 ​​bits, qui peut être due à une arithmétique 64 bits plus rapide (voir le premier commentaire).
Bob le

1
(«flag var» & «flag value») != 0ne fonctionne pas pour moi. La condition échoue toujours et mon compilateur (Mono 2.6.5 d'Unity3D) signale un «avertissement CS0162: code inaccessible détecté» lorsqu'il est utilisé dans un fichierif (…) .
Slipp

1
@ wraith808: J'ai réalisé que l'erreur était de faire avec mon test que vous aviez correct dans le vôtre - la puissance de 2 … = 1, … = 2, … = 4sur les valeurs d'énumération est d'une importance critique lors de l'utilisation [Flags]. Je supposais qu'il commencerait les entrées à 1et avancerait automatiquement par Po2s. Le comportement semble cohérent dans MS .NET et Mono daté de Unity. Mes excuses vous ont mis cela.
Slipp D.Thompson

21

J'ai mis en place une méthode d'extension pour le faire: question connexe .

Fondamentalement:

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

Ensuite, vous pouvez faire:

FlagTests testItem = FlagTests.Flag1 | FlagTests.Flag2;

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

Incidemment, la convention que j'utilise pour les énumérations est au singulier pour le standard, au pluriel pour les drapeaux. De cette façon, vous savez à partir du nom enum s'il peut contenir plusieurs valeurs.


Cela devrait être un commentaire, mais comme je suis un nouvel utilisateur, il semble que je ne peux pas encore ajouter de commentaires ... public static bool IsSet (this Enum input, Enum matchTo) {return (Convert.ToUInt32 (input) & Convert .ToUInt32 (matchTo))! = 0; } Existe-t-il un moyen d'être compatible avec n'importe quel type d'énumération (car ici cela ne fonctionnera pas si votre enum est de type UInt64 ou peut avoir des valeurs négatives)?
user276648

C'est assez redondant avec Enum.HasFlag (Enum) (disponible en .net 4.0)
PPC

1
@PPC Je ne dirais pas exactement redondant - beaucoup de gens développent sur des versions plus anciennes du framework. Vous avez raison cependant, les utilisateurs de .Net 4 devraient utiliser l' HasFlagextension à la place.
Keith

4
@Keith: De plus, il y a une différence notable: ((FlagTest) 0x1) .HasFlag (0x0) retournera true, ce qui peut ou non être un comportement souhaité
PPC

19

Un conseil de plus ... Ne faites jamais la vérification binaire standard avec le drapeau dont la valeur est "0". Votre vérification sur ce drapeau sera toujours vraie.

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

Si vous vérifiez le paramètre d'entrée binaire par rapport à FullInfo - vous obtenez:

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

bPRez sera toujours vrai comme TOUT & 0 toujours == 0.


Au lieu de cela, vous devez simplement vérifier que la valeur de l'entrée est 0:

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

Je viens de corriger un tel bogue de 0 drapeau. Je pense que c'est une erreur de conception dans .NET Framework (3.5) car vous devez savoir laquelle des valeurs d'indicateur est 0 avant de la tester.
thersch

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

5

Pour les opérations sur les bits, vous devez utiliser des opérateurs de bits.

Cela devrait faire l'affaire:

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

Edit: Correction de mon if check - je suis retourné dans mes méthodes C / C ++ (merci à Ryan Farley pour l'avoir signalé)


5

Concernant l'édition. Vous ne pouvez pas le rendre vrai. Je vous suggère d'envelopper ce que vous voulez dans une autre classe (ou méthode d'extension) pour vous rapprocher de la syntaxe dont vous avez besoin.

c'est à dire

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

4

Essaye ça:


if ((testItem & FlagTest.Flag1) == FlagTest.Flag1)
{
    // do something
}
Fondamentalement, votre code demande si le fait d'avoir les deux indicateurs définis équivaut à avoir un indicateur défini, ce qui est évidemment faux. Le code ci-dessus ne laissera que le bit Flag1 défini s'il est défini, puis compare ce résultat à Flag1.


1

même sans [Flags], vous pouvez utiliser quelque chose comme ça

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

ou si vous avez une énumération de valeur zéro

if((testItem & (FlagTest.Flag1 | FlagTest.Flag2 ))!=FlagTest.None){
//..
}
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.