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


3 Answers

En .NET 4 ahora puede escribir:

flags.HasFlag(FlagsEnum.Bit4)
Question

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 sintaxis de C # usando una [Encuesta de Banderas]?




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

  }
}



@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.




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 here .






Related