Pendant toute ma vie, je ne me rappelle plus comment définir, supprimer, basculer ou tester un bit dans un champ de bits. Je ne suis pas sûr ou je les mélange parce que j'ai rarement besoin de ceux-ci. Donc, une "feuille de triche" 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 en syntaxe C # avec une énumération [Flags]?
J'ai encore travaillé sur ces extensions - Vous pouvez trouver le code ici
J'ai écrit certaines 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
Dans .NET 4, vous pouvez maintenant écrire:
flags.HasFlag(FlagsEnum.Bit4)
L'idiome est d'utiliser l'opérateur binaire ou égal pour définir les bits:
flags |= 0x04;
Pour effacer un peu, l’idiome est d’utiliser bit à bit et avec négation:
flags &= ~0x04;
Parfois, vous avez un décalage qui identifie votre bit, puis l'idiome est d'utiliser ces éléments combinés avec un décalage à gauche:
flags |= 1 << offset;
flags &= ~(1 << offset);
@A dessiné
Notez que, sauf dans les cas les plus simples, Enum.HasFlag est très pénalisant en termes de performances par rapport à l'écriture manuelle du code. Considérons 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 au niveau du bit standard.
Les opérations intégrées d'énumération de drapeau de .NET sont malheureusement assez limitées. La plupart du temps, les utilisateurs doivent comprendre la logique de fonctionnement au niveau du bit.
Dans .NET 4, la méthode HasFlag
a été ajoutée à Enum
, ce qui permet de simplifier le code de l'utilisateur, mais elle pose malheureusement de nombreux problèmes.
HasFlag
n'est pas sûr du type car il accepte tout type d'argument de valeur enum, pas seulement le type enum donné.HasFlag
est ambigu quant à savoir si elle vérifie si la valeur contient tout ou partie des indicateurs fournis par l'argument enum value. C'est tout au fait.HasFlag
est plutôt lent car il nécessite une boxe qui provoque des allocations et donc davantage de corbeilles.En partie à cause de la prise en charge limitée par .NET pour les énumérations de drapeaux, j’ai écrit la bibliothèque OSS Enums.NET , qui résout chacun de ces problèmes et facilite beaucoup le traitement des énumérations de drapeaux.
Vous trouverez ci-dessous certaines des opérations qu’il fournit ainsi que leurs implémentations équivalentes en utilisant uniquement le framework .NET.
.NET flags | otherFlags
Enums.NET flags.CombineFlags(otherFlags)
.NET flags & ~otherFlags
Enums.NET flags.RemoveFlags(otherFlags)
.NET flags & otherFlags
Enums.NET flags.CommonFlags(otherFlags)
.NET flags ^ otherFlags
Enums.NET flags.ToggleFlags(otherFlags)
.NET (flags & otherFlags) == otherFlags
ou flags.HasFlag(otherFlags)
Enums.NET flags.HasAllFlags(otherFlags)
.NET (flags & otherFlags) != 0
Enums.NET flags.HasAnyFlags(otherFlags)
.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'intégrer ces améliorations à .NET Core et éventuellement à l'intégralité du .NET Framework. Vous pouvez consulter ma proposition ici .
Syntaxe C++, en supposant que le bit 0 est un bit de poids faible, en supposant que les indicateurs sont non signés longtemps:
Vérifiez si réglé:
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))
Pour tester un bit, procédez comme suit: (en supposant que flags est un nombre de 32 bits)
Bit de test:
if((flags & 0x08) == 0x08)
flags = flags ^ 0x08;
flags = flags & 0xFFFFFF7F;
Cela a été inspiré en utilisant Sets en tant qu'indexeurs dans Delphi, il y a bien 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];
}
}
Les opérations C++ sont: & | ^ ~ (pour et, ou, xor et pas au niveau des bits). >> et <<, qui sont des opérations bitshift, sont également intéressants.
Donc, pour tester si un bit est défini dans un drapeau, utilisez: if (flags & 8) // teste le bit 4
Pour la meilleure performance et zéro déchet, 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;
}
}
}