Opérations au niveau du bit C # les plus courantes sur les énumérations


201

Pour la vie de moi, je ne me souviens pas comment définir, supprimer, basculer ou tester un peu dans un champ de bits. Soit je ne suis pas sûr, soit je les mélange parce que j'en ai rarement besoin. Donc, un "bit-cheat-sheet" serait bien d'avoir.

Par exemple:

flags = flags | FlagsEnum.Bit4;  // Set bit 4.

ou

if ((flags & FlagsEnum.Bit4)) == FlagsEnum.Bit4) // Is there a less verbose way?

Pouvez-vous donner des exemples de toutes les autres opérations courantes, de préférence dans la syntaxe C # en utilisant une énumération [Flags]?


5
Cela a déjà été répondu ici
Greg Rogers

7
dommage que ce lien n'apparaisse pas dans les conseils de question pour ce sujet.
cori

10
Cette question est balisée pour c / c ++, donc quelqu'un qui cherche des informations sur C # n'y verra probablement pas même si la syntaxe semble être la même.
Adam Lassek

Je ne connais pas de façon moins verbeuse de faire le test de bits
Andy Johnson

2
@Andy, il existe maintenant une API pour le test de bits dans .NET 4.
Drew Noakes

Réponses:


288

J'ai fait un peu plus de travail sur ces extensions - Vous pouvez trouver le code ici

J'ai écrit quelques méthodes d'extension qui étendent System.Enum que j'utilise souvent ... Je ne prétends pas qu'elles sont à l'épreuve des balles, mais elles ont aidé ... Commentaires supprimés ...

namespace Enum.Extensions {

    public static class EnumerationExtensions {

        public static bool Has<T>(this System.Enum type, T value) {
            try {
                return (((int)(object)type & (int)(object)value) == (int)(object)value);
            } 
            catch {
                return false;
            }
        }

        public static bool Is<T>(this System.Enum type, T value) {
            try {
                return (int)(object)type == (int)(object)value;
            }
            catch {
                return false;
            }    
        }


        public static T Add<T>(this System.Enum type, T value) {
            try {
                return (T)(object)(((int)(object)type | (int)(object)value));
            }
            catch(Exception ex) {
                throw new ArgumentException(
                    string.Format(
                        "Could not append value from enumerated type '{0}'.",
                        typeof(T).Name
                        ), ex);
            }    
        }


        public static T Remove<T>(this System.Enum type, T value) {
            try {
                return (T)(object)(((int)(object)type & ~(int)(object)value));
            }
            catch (Exception ex) {
                throw new ArgumentException(
                    string.Format(
                        "Could not remove value from enumerated type '{0}'.",
                        typeof(T).Name
                        ), ex);
            }  
        }

    }
}

Ensuite, ils sont utilisés comme suit

SomeType value = SomeType.Grapes;
bool isGrapes = value.Is(SomeType.Grapes); //true
bool hasGrapes = value.Has(SomeType.Grapes); //true

value = value.Add(SomeType.Oranges);
value = value.Add(SomeType.Apples);
value = value.Remove(SomeType.Grapes);

bool hasOranges = value.Has(SomeType.Oranges); //true
bool isApples = value.Is(SomeType.Apples); //false
bool hasGrapes = value.Has(SomeType.Grapes); //false

1
J'ai également trouvé cela utile - des idées sur la façon de le modifier pour qu'il fonctionne sur n'importe quel type sous-jacent?
Charlie Salts le

7
Ces extensions ont juste fait ma journée, ma semaine, mon mois et très probablement mon année.
thaBadDawg

Je vous remercie! Tout le monde: assurez-vous de vérifier la mise à jour qu'Hugoware a liée.
Helge Klein

Un très bel ensemble d'extensions. C'est dommage qu'ils nécessitent de la boxe, bien que je ne puisse pas penser à une alternative qui n'utilise pas la boxe et qui soit aussi succincte. Même la nouvelle HasFlagméthode Enumnécessite la boxe.
Drew Noakes

4
@Drew: Voir code.google.com/p/unconstrained-melody pour un moyen d'éviter la boxe :)
Jon Skeet

109

Dans .NET 4, vous pouvez maintenant écrire:

flags.HasFlag(FlagsEnum.Bit4)

4
+1 pour avoir souligné cela, bien que ce FlagsEnumsoit un nom moche. :)
Jim Schubert

2
@Jim, peut-être. Il s'agit simplement d'un exemple de nom, tel qu'il est utilisé dans la question d'origine, vous êtes donc libre de le modifier dans votre code.
Drew Noakes

14
Je connais! Mais les noms laids sont comme IE6 et ne disparaîtront probablement jamais :(
Jim Schubert

5
@JimSchubert, encore une fois, je viens de reproduire le nom du type de la question d'origine afin de ne pas confondre le problème. Les directives de dénomination des types d'énumération .NET indiquent que toutes les énumérations[Flags] doivent avoir des noms pluralisés, de sorte que le nom FlagsEnumprésente des problèmes encore plus graves que la laideur.
Drew Noakes

1
Je recommande également les directives de conception de framework: conventions, idiomes et modèles pour les bibliothèques .NET réutilisables . Il est un peu cher à acheter, mais je pense que Safari Online et Books24x7 l'offrent tous deux aux abonnés.
Jim Schubert

89

L'idiome est d'utiliser l'opérateur au niveau du bit ou égal pour définir les bits:

flags |= 0x04;

Pour effacer un peu, l'idiome est d'utiliser au niveau du bit et avec négation:

flags &= ~0x04;

Parfois, vous avez un décalage qui identifie votre bit, puis l'idiome est de les utiliser en combinaison avec le décalage à gauche:

flags |= 1 << offset;
flags &= ~(1 << offset);

22

@A dessiné

Notez que, sauf dans le cas le plus simple, Enum.HasFlag comporte une lourde pénalité de performance 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.


10
Bien que certainement intéressant et bon à souligner. Vous devez considérer l'utilisation. Selon cela, si vous n'effectuez pas quelques centaines de milliers d'opérations ou plus, vous ne le remarquerez probablement même pas.
Joshua Hayes

7
La HasFlagméthode implique la boxe / unboxing, ce qui explique cette différence. Mais le coût est si insignifiant (0,4 µs) qu'à moins que vous ne soyez dans une boucle serrée, je prendrais l'appel d'API déclarative plus lisible (et moins probablement buggé) n'importe quel jour.
Drew Noakes du

8
Selon l'utilisation, cela pourrait être un problème. Et comme je travaille un peu avec les chargeurs, j'ai pensé que c'était bon de le souligner.
Chuck Dee

11

Les opérations d'énumération des drapeaux intégrées de .NET sont malheureusement assez limitées. La plupart du temps, les utilisateurs doivent déterminer la logique de fonctionnement au niveau du bit.

Dans .NET 4, la méthode a HasFlagété ajoutée, Enumce qui permet de simplifier le code de l'utilisateur, mais malheureusement, il présente de nombreux problèmes.

  1. HasFlag n'est pas de type sécurisé car il accepte tout type d'argument de valeur enum, pas seulement le type d'énumération donné.
  2. HasFlagest ambigu quant à savoir s'il vérifie si la valeur a tout ou partie des indicateurs fournis par l'argument de valeur enum. C'est tout au fait.
  3. HasFlag est assez lent car il nécessite une boxe qui provoque des allocations et donc plus de récupérations de place.

En partie à cause de la prise en charge limitée de .NET pour les énumérations d'indicateurs, j'ai écrit la bibliothèque OSS Enums.NET qui résout chacun de ces problèmes et facilite la gestion des énumérations d'indicateurs.

Vous trouverez ci-dessous certaines des opérations qu'il fournit ainsi que leurs implémentations équivalentes utilisant uniquement le framework .NET.

Combiner les drapeaux

.NET             flags | otherFlags

Enums.NET flags.CombineFlags(otherFlags)


Supprimer les drapeaux

.NET             flags & ~otherFlags

Enums.NET flags.RemoveFlags(otherFlags)


Drapeaux communs

.NET             flags & otherFlags

Enums.NET flags.CommonFlags(otherFlags)


Basculer les drapeaux

.NET             flags ^ otherFlags

Enums.NET flags.ToggleFlags(otherFlags)


A tous les drapeaux

.NET             (flags & otherFlags) == otherFlags ouflags.HasFlag(otherFlags)

Enums.NET flags.HasAllFlags(otherFlags)


A des drapeaux

.NET             (flags & otherFlags) != 0

Enums.NET flags.HasAnyFlags(otherFlags)


Obtenez des drapeaux

.NET

Enumerable.Range(0, 64)
  .Where(bit => ((flags.GetTypeCode() == TypeCode.UInt64 ? (long)(ulong)flags : Convert.ToInt64(flags)) & (1L << bit)) != 0)
  .Select(bit => Enum.ToObject(flags.GetType(), 1L << bit))`

Enums.NET flags.GetFlags()


J'essaie d'obtenir ces améliorations incorporées dans .NET Core et peut-être éventuellement le .NET Framework complet. Vous pouvez consulter ma proposition ici .


7

Syntaxe C ++, en supposant que le bit 0 est LSB, en supposant que les drapeaux ne sont pas longs:

Vérifiez si défini:

flags & (1UL << (bit to test# - 1))

Vérifiez si non défini:

invert test !(flag & (...))

Ensemble:

flag |= (1UL << (bit to set# - 1))

Clair:

flag &= ~(1UL << (bit to clear# - 1))

Basculer:

flag ^= (1UL << (bit to set# - 1))

3

Pour les meilleures performances et zéro déchets, utilisez ceci:

using System;
using T = MyNamespace.MyFlags;

namespace MyNamespace
{
    [Flags]
    public enum MyFlags
    {
        None = 0,
        Flag1 = 1,
        Flag2 = 2
    }

    static class MyFlagsEx
    {
        public static bool Has(this T type, T value)
        {
            return (type & value) == value;
        }

        public static bool Is(this T type, T value)
        {
            return type == value;
        }

        public static T Add(this T type, T value)
        {
            return type | value;
        }

        public static T Remove(this T type, T value)
        {
            return type & ~value;
        }
    }
}

2

Pour tester un peu, vous devez procéder comme suit: (en supposant que les drapeaux sont un nombre de 32 bits)

Bit de test:

if((flags & 0x08) == 0x08)
(Si le bit 4 est défini, il est vrai) Basculer vers l'arrière (1 - 0 ou 0 - 1):
flags = flags ^ 0x08;
Remettre le bit 4 à zéro:
flags = flags & 0xFFFFFF7F;


2
-1 puisque cela ne dérange même pas avec les énumérations? De plus, le codage manuel des valeurs est fragile ... J'écrirais au moins ~0x08au lieu de 0xFFFFFFF7... (le masque réel pour 0x8)
Ben Mosher

1
Au début, je pensais que Ben's -1 était dur, mais l'utilisation de "0xFFFFFF7F" en fait un exemple particulièrement médiocre.
ToolmakerSteve

2

Cela a été inspiré par l'utilisation de Sets comme indexeurs dans Delphi, il y a longtemps:

/// Example of using a Boolean indexed property
/// to manipulate a [Flags] enum:

public class BindingFlagsIndexer
{
  BindingFlags flags = BindingFlags.Default;

  public BindingFlagsIndexer()
  {
  }

  public BindingFlagsIndexer( BindingFlags value )
  {
     this.flags = value;
  }

  public bool this[BindingFlags index]
  {
    get
    {
      return (this.flags & index) == index;
    }
    set( bool value )
    {
      if( value )
        this.flags |= index;
      else
        this.flags &= ~index;
    }
  }

  public BindingFlags Value 
  {
    get
    { 
      return flags;
    } 
    set( BindingFlags value ) 
    {
      this.flags = value;
    }
  }

  public static implicit operator BindingFlags( BindingFlagsIndexer src )
  {
     return src != null ? src.Value : BindingFlags.Default;
  }

  public static implicit operator BindingFlagsIndexer( BindingFlags src )
  {
     return new BindingFlagsIndexer( src );
  }

}

public static class Class1
{
  public static void Example()
  {
    BindingFlagsIndexer myFlags = new BindingFlagsIndexer();

    // Sets the flag(s) passed as the indexer:

    myFlags[BindingFlags.ExactBinding] = true;

    // Indexer can specify multiple flags at once:

    myFlags[BindingFlags.Instance | BindingFlags.Static] = true;

    // Get boolean indicating if specified flag(s) are set:

    bool flatten = myFlags[BindingFlags.FlattenHierarchy];

    // use | to test if multiple flags are set:

    bool isProtected = ! myFlags[BindingFlags.Public | BindingFlags.NonPublic];

  }
}

2
Cela ne compile même pas si BindingFlags est une énumération d'octets: this.flags & = ~ index;
Amuliar

0

Les opérations C ++ sont: & | ^ ~ (pour les opérations et, ou, xor et non au niveau du bit). Sont également intéressants >> et <<, qui sont des opérations de décalage de bits.

Donc, pour tester la présence d'un bit dans un drapeau, vous utiliseriez: if (flags & 8) // teste le bit 4 a été défini


8
La question concerne c #, pas c ++
Andy Johnson

3
D'autre part, C # utilise les mêmes opérateurs: msdn.microsoft.com/en-us/library/6a71f45d.aspx
ToolmakerSteve

3
Pour la défense de @ workmad3, les balises d'origine avaient C et C ++
pqsk
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.