Las operaciones bit a bit más comunes de C # en enumeraciones

Por mi vida, no recuerdo cómo configurar, borrar, alternar o probar un bit en un campo de bits. O no estoy seguro o los mezclo porque rara vez los necesito. Así que sería bueno tener una “hoja de trucos”.

Por ejemplo:

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

o

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

¿Puedes dar ejemplos de todas las otras operaciones comunes, preferiblemente en syntax de C # usando una [Encuesta de Banderas]?

Trabajé un poco más en estas extensiones. Puedes encontrar el código aquí

Escribí algunos métodos de extensión que extienden System.Enum que uso a menudo … No afirmo que sean a prueba de balas, pero me han ayudado … Comentarios eliminados …

 namespace Enum.Extensions { public static class EnumerationExtensions { public static bool Has(this System.Enum type, T value) { try { return (((int)(object)type & (int)(object)value) == (int)(object)value); } catch { return false; } } public static bool Is(this System.Enum type, T value) { try { return (int)(object)type == (int)(object)value; } catch { return false; } } public static T Add(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(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); } } } } 

Luego se usan como los siguientes

 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 

En .NET 4 ahora puede escribir:

 flags.HasFlag(FlagsEnum.Bit4) 

La expresión idiomática es usar el operador bit a bit o-equal para establecer bits:

 flags |= 0x04; 

Para borrar un poco, la expresión idiomática es usar bitwise y con negación:

 flags &= ~0x04; 

A veces tiene un desplazamiento que identifica su bit, y luego el modismo es usar estos combinados con desplazamiento a la izquierda:

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

@Dibujó

Tenga en cuenta que, excepto en los casos más simples, Enum.HasFlag conlleva una gran penalización de rendimiento en comparación con escribir el código manualmente. Considera el siguiente código:

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

Más de 10 millones de iteraciones, el método de extensión HasFlags toma la friolera de 4793 ms, en comparación con los 27 ms para la implementación bit a bit estándar.

Sintaxis de C ++, suponiendo que el bit 0 es LSB, suponiendo que flags no tiene signo largo:

Marque si Set:

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

Verifique si no está configurado:

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

Conjunto:

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

Claro:

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

Palanca:

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

Las operaciones de enumeración de indicadores incorporadas de .NET son, lamentablemente, bastante limitadas. La mayoría de las veces, los usuarios tienen que descifrar la lógica de operación bit a bit.

En .NET 4, el método HasFlag se agregó a Enum que ayuda a simplificar el código del usuario, pero desafortunadamente hay muchos problemas con él.

  1. HasFlag no es seguro para tipos ya que acepta cualquier tipo de argumento de valor enum, no solo el tipo de enumeración dado.
  2. HasFlag es ambiguo en cuanto a si comprueba si el valor tiene todos o alguno de los indicadores proporcionados por el argumento de valor enum. Todo por cierto.
  3. HasFlag es bastante lento ya que requiere un boxeo que causa asignaciones y, por lo tanto, más recolecciones de basura.

Debido en parte al soporte limitado de .NET para las enumeraciones de banderas, escribí la biblioteca OSS Enums.NET que aborda cada uno de estos problemas y hace que manejar enum sea mucho más fácil.

A continuación, se incluyen algunas de las operaciones que proporciona junto con sus implementaciones equivalentes utilizando solo .NET Framework.

Combine banderas

flags | otherFlags .NET flags | otherFlags flags | otherFlags

Enums.NET flags.CombineFlags(otherFlags)


Eliminar banderas

.NET flags & ~otherFlags

Enums.NET flags.RemoveFlags(otherFlags)


Banderas comunes

.NET flags & otherFlags

Enums.NET flags.CommonFlags(otherFlags)


Alternar banderas

.NET flags ^ otherFlags

Enums.NET flags.ToggleFlags(otherFlags)


Tiene todas las banderas

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

Enums.NET flags.HasAllFlags(otherFlags)


Tiene banderas

.NET (flags & otherFlags) != 0

Enums.NET flags.HasAnyFlags(otherFlags)


Obtener banderas

.RED

 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()


Estoy tratando de incorporar estas mejoras en .NET Core y, tal vez, eventualmente en el .NET Framework completo. Puedes ver mi propuesta aquí .

Para probar un poco, haría lo siguiente: (asumiendo que flags es un número de 32 bits)

Bit de prueba:

 if((flags & 0x08) == 0x08) 

(Si el bit 4 está configurado, entonces es verdadero) Alternar hacia atrás (1 – 0 o 0 – 1):

 flags = flags ^ 0x08; 

Restablecer Bit 4 a cero:

 flags = flags & 0xFFFFFF7F; 

Esto se inspiró en el uso de Sets como indexadores en Delphi, hace mucho tiempo cuando:

 /// 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]; } } 

Las operaciones de C ++ son: & | ^ ~ (para operaciones and, or, xor y no bitwise). También son de interés >> y <<, que son operaciones bitshift.

Entonces, para probar que un bit se establezca en una bandera, deberías usar: if (flags & 8) // tests bit 4 ha sido establecido