Operaciones bit a bit más comunes de C # en enumeraciones

201

Por mi vida, no puedo recordar cómo configurar, eliminar, alternar o probar un poco en un campo de bits. O no estoy seguro o los mezclo porque rara vez los necesito. Por lo tanto, 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?

¿Puede dar ejemplos de todas las demás operaciones comunes, preferiblemente en sintaxis de C # usando una enumeración [Flags]?

steffenj
fuente
55
Esto ha sido respondido antes aquí
Greg Rogers
77
Lástima que el enlace no aparezca en las sugerencias de preguntas para este tema.
cori
10
Sin embargo, esa pregunta está etiquetada para c / c ++, por lo que alguien que busque información sobre C # probablemente no buscaría allí a pesar de que la sintaxis parece ser la misma.
Adam Lassek
No conozco una forma menos detallada de hacer la prueba de bits
Andy Johnson
2
@Andy, ahora hay una API para la prueba de bits en .NET 4.
Drew Noakes

Respuestas:

288

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

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

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

    }
}

Luego se usan de la siguiente manera

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
Hugoware
fuente
1
También encontré esto útil: ¿alguna idea de cómo puedo modificarlo para que funcione en cualquier tipo subyacente?
Sales de Charlie
77
Estas extensiones simplemente hicieron mi día, mi semana, mi mes y posiblemente mi año.
thaBadDawg
¡Gracias! Todos: asegúrese de consultar la actualización que Hugoware ha vinculado.
Helge Klein
Un muy buen conjunto de extensiones. Es una pena que requieran boxeo, aunque no puedo pensar en una alternativa que no use el boxeo y sea sucinta. Incluso el nuevo HasFlagmétodo Enumrequiere el boxeo.
Drew Noakes
44
@Drew: Ver code.google.com/p/unconstrained-melody para una forma de evitar el boxeo :)
Jon Skeet
109

En .NET 4 ahora puede escribir:

flags.HasFlag(FlagsEnum.Bit4)
Drew Noakes
fuente
44
+1 por señalar eso, aunque FlagsEnumes un nombre feo. :)
Jim Schubert
2
@ Jim, tal vez. Es solo un nombre de muestra, como se usa en la pregunta original, por lo que puede cambiarlo en su código.
Drew Noakes
14
¡Lo sé! Pero los nombres feos son como IE6 y probablemente nunca desaparecerán :(
Jim Schubert
55
@JimSchubert, nuevamente, acabo de reproducir el nombre del tipo de la pregunta original para no confundir el problema. Las Pautas de nomenclatura de tipo de enumeración .NET indican que todas las [Flags]enumeraciones deben tener nombres en plural, por lo que el nombre FlagsEnumtiene problemas aún más graves que la fealdad.
Drew Noakes
1
También recomiendo pautas de diseño de marco: convenciones, expresiones idiomáticas y patrones para bibliotecas .NET reutilizables . Es un poco caro de comprar, pero creo que Safari Online y Books24x7 lo ofrecen para suscriptores.
Jim Schubert
89

El idioma es usar el operador bit a bit o igual para establecer bits:

flags |= 0x04;

Para aclarar un poco, el idioma es usar bit a bit y con negación:

flags &= ~0x04;

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

flags |= 1 << offset;
flags &= ~(1 << offset);
Stephen Deken
fuente
22

@Dibujó

Tenga en cuenta que, excepto en los casos más simples, Enum.HasFlag conlleva una fuerte penalización de rendimiento en comparación con escribir el código manualmente. Considere 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.

Chuck Dee
fuente
10
Aunque sin duda interesante y bueno para señalar. Necesitas considerar el uso. De acuerdo con esto, si no está realizando un par de cientos de miles o más operaciones, probablemente ni siquiera se dará cuenta de esto.
Joshua Hayes
77
El HasFlagmétodo implica boxing / unboxing, lo que explica esta diferencia. Pero el costo es tan trivial (0.4 µs) que, a menos que esté en un círculo cerrado, tomaría la llamada declarativa API más legible (y menos probable con errores) cualquier día.
Drew Noakes
8
Dependiendo del uso, podría ser un problema. Y como trabajo bastante con los cargadores, pensé que era bueno señalarlo.
Chuck Dee
11

Desafortunadamente, las operaciones de enum de bandera incorporadas de .NET son bastante limitadas. La mayoría de las veces los usuarios se quedan con la lógica de operación bit a bit.

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

  1. HasFlag no es de tipo seguro ya que acepta cualquier tipo de argumento de valor de enumeración, no solo el tipo de enumeración dado.
  2. HasFlages ambiguo en cuanto a si verifica si el valor tiene todas o alguna de las banderas proporcionadas por el argumento de valor enum. Es 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 enums de flag, escribí la biblioteca de OSS Enums.NET que aborda cada uno de estos problemas y hace que el manejo de las enums de flag sea mucho más fácil.

A continuación se presentan algunas de las operaciones que proporciona junto con sus implementaciones equivalentes utilizando solo el marco .NET.

Combinar banderas

.RED             flags | otherFlags

Enums.NET flags.CombineFlags(otherFlags)


Eliminar banderas

.RED             flags & ~otherFlags

Enums.NET flags.RemoveFlags(otherFlags)


Banderas Comunes

.RED             flags & otherFlags

Enums.NET flags.CommonFlags(otherFlags)


Alternar banderas

.RED             flags ^ otherFlags

Enums.NET flags.ToggleFlags(otherFlags)


Tiene todas las banderas

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

Enums.NET flags.HasAllFlags(otherFlags)


Tiene banderas

.RED             (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 quizás eventualmente en el .NET Framework completo. Puedes ver mi propuesta aquí .

TylerBrinkley
fuente
7

Sintaxis de C ++, suponiendo que el bit 0 es LSB, suponiendo que las banderas no tengan signo largo:

Compruebe si está configurado:

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

Verificar 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))
Petesh
fuente
3

Para el mejor rendimiento y cero basura, use esto:

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;
        }
    }
}
Mark Bamford
fuente
2

Para probar un bit, haría lo siguiente: (suponiendo que las banderas son 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 el bit 4 a cero:
flags = flags & 0xFFFFFF7F;

Nashirak
fuente
2
-1 ya que esto ni siquiera molesta con las enumeraciones? Además, la codificación manual de los valores es frágil ... Al menos escribiría en ~0x08lugar de 0xFFFFFFF7... (la máscara real para 0x8)
Ben Mosher
1
Al principio estaba pensando que el -1 de Ben era duro, pero el uso de "0xFFFFFF7F" hace que este sea un ejemplo especialmente pobre.
ToolmakerSteve
2

Esto se inspiró al usar Conjuntos como indexadores en Delphi, 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];

  }
}
Tony Tanzillo
fuente
2
Esto incluso no se compila si BindingFlags es byte enum: this.flags & = ~ index;
amuliar el
0

Las operaciones de C ++ son: & | ^ ~ (para y, o, xor y operaciones no bit a bit). También son de interés >> y <<, que son operaciones de desplazamiento de bits.

Entonces, para probar si un bit se establece en un indicador, usaría: if (flags & 8) // prueba el bit 4 se ha establecido

workmad3
fuente
8
La pregunta se refiere a c #, no a c ++
Andy Johnson
3
Por otro lado, C # utiliza los mismos operadores: msdn.microsoft.com/en-us/library/6a71f45d.aspx
ToolmakerSteve
3
En defensa de @ workmad3, las etiquetas originales tenían C y C ++
pqsk