Usando una máscara de bits en C #

97

Digamos que tengo lo siguiente

int susan = 2; //0010
int bob = 4; //0100
int karen = 8; //1000

y paso 10 (8 + 2) como parámetro a un método y quiero decodificar esto para que signifique susan y karen

Yo se que 10 es 1010

pero ¿cómo puedo hacer algo de lógica para ver si un bit específico está marcado como en

if (condition_for_karen) // How to quickly check whether effective karen bit is 1

En este momento, todo lo que puedo pensar es verificar si el número que pasé es

14 // 1110
12 // 1100
10 // 1010
8 //  1000

Cuando tengo una mayor cantidad de bits reales en mi escenario del mundo real, esto parece poco práctico, ¿cuál es una mejor manera de usar una máscara para verificar si cumplo o no con la condición para solo Karen?

Puedo pensar en desplazarme a la izquierda y luego volver a cambiar a la derecha y luego volver a borrar partes distintas de la que me interesa, pero esto también parece demasiado complejo.

Mate
fuente
7
Solo tuve que comentar sobre el uso. Si está realizando operaciones con bits, solo debe utilizar operadores de manipulación de bits. es decir, piense en ello como (8 | 2), no (8 + 2).
Jeff Mercado
A Karen también le gustaría hablar con su gerente de inmediato.
Krythic

Respuestas:

198

La forma tradicional de hacer esto es usar el Flagsatributo en un enum:

[Flags]
public enum Names
{
    None = 0,
    Susan = 1,
    Bob = 2,
    Karen = 4
}

Luego, buscaría un nombre particular de la siguiente manera:

Names names = Names.Susan | Names.Bob;

// evaluates to true
bool susanIsIncluded = (names & Names.Susan) != Names.None;

// evaluates to false
bool karenIsIncluded = (names & Names.Karen) != Names.None;

Las combinaciones lógicas bit a bit pueden ser difíciles de recordar, así que me facilito la vida con una FlagsHelperclase *:

// The casts to object in the below code are an unfortunate necessity due to
// C#'s restriction against a where T : Enum constraint. (There are ways around
// this, but they're outside the scope of this simple illustration.)
public static class FlagsHelper
{
    public static bool IsSet<T>(T flags, T flag) where T : struct
    {
        int flagsValue = (int)(object)flags;
        int flagValue = (int)(object)flag;

        return (flagsValue & flagValue) != 0;
    }

    public static void Set<T>(ref T flags, T flag) where T : struct
    {
        int flagsValue = (int)(object)flags;
        int flagValue = (int)(object)flag;

        flags = (T)(object)(flagsValue | flagValue);
    }

    public static void Unset<T>(ref T flags, T flag) where T : struct
    {
        int flagsValue = (int)(object)flags;
        int flagValue = (int)(object)flag;

        flags = (T)(object)(flagsValue & (~flagValue));
    }
}

Esto me permitiría reescribir el código anterior como:

Names names = Names.Susan | Names.Bob;

bool susanIsIncluded = FlagsHelper.IsSet(names, Names.Susan);

bool karenIsIncluded = FlagsHelper.IsSet(names, Names.Karen);

Tenga en cuenta que también podría agregar Karenal conjunto haciendo esto:

FlagsHelper.Set(ref names, Names.Karen);

Y podría eliminar Susande manera similar:

FlagsHelper.Unset(ref names, Names.Susan);

* Como Porges señaló, un equivalente del IsSetmétodo anterior ya existe en .NET 4.0: Enum.HasFlag. Sin embargo, los métodos Sety Unsetno parecen tener equivalentes; así que todavía diría que esta clase tiene algún mérito.


Nota: El uso de enumeraciones es solo la forma convencional de abordar este problema. Puede traducir totalmente todo el código anterior para usar ints en su lugar y funcionará igual de bien.

Dan Tao
fuente
14
+1 por ser el primer código que realmente funciona. También puede hacerlo (names & Names.Susan) == Names.Susan, que no requiere un None.
Matthew Flaschen
1
@Matthew: Oh, sí, buen punto. Supongo que me acostumbro a definir siempre un Nonevalor para todas mis enumeraciones, ya que me parece que resulta conveniente en muchos escenarios.
Dan Tao
30
Esto está integrado, no necesita sus métodos de ayuda ...var susanIsIncluded = names.HasFlag(Names.Susan);
porges
2
@Porges: Vaya, no tengo idea de cómo me perdí eso ... ¡gracias por señalarlo! (Sin embargo, parece que solo está disponible a partir de .NET 4.0 ... además, no hay equivalente para el Setmétodo. Por lo tanto, diría que los métodos auxiliares no son al menos totalmente inútiles).
Dan Tao
6
Tenga en cuenta que usar names.HasFlag(Names.Susan)es como lo (names & Names.Susan) == Names.Susanque no siempre es como (names & Names.Susan) != Names.None. Por ejemplo, si names.HasFlag(Names.none)names.HasFlag(Names.Susan|Names.Karen)
marca
20
if ( ( param & karen ) == karen )
{
  // Do stuff
}

El bit a bit "y" ocultará todo excepto el bit que "representa" a Karen. Siempre que cada persona esté representada por una posición de un solo bit, puede verificar a varias personas con un simple:

if ( ( param & karen ) == karen )
{
  // Do Karen's stuff
}
if ( ( param & bob ) == bob )
  // Do Bob's stuff
}
eldarerathis
fuente
12

He incluido un ejemplo aquí que demuestra cómo podría almacenar la máscara en una columna de la base de datos como un int, y cómo restablecería la máscara más adelante:

public enum DaysBitMask { Mon=0, Tues=1, Wed=2, Thu = 4, Fri = 8, Sat = 16, Sun = 32 }


DaysBitMask mask = DaysBitMask.Sat | DaysBitMask.Thu;
bool test;
if ((mask & DaysBitMask.Sat) == DaysBitMask.Sat)
    test = true;
if ((mask & DaysBitMask.Thu) == DaysBitMask.Thu)
    test = true;
if ((mask & DaysBitMask.Wed) != DaysBitMask.Wed)
    test = true;

// Store the value
int storedVal = (int)mask;

// Reinstate the mask and re-test
DaysBitMask reHydratedMask = (DaysBitMask)storedVal;

if ((reHydratedMask & DaysBitMask.Sat) == DaysBitMask.Sat)
    test = true;
if ((reHydratedMask & DaysBitMask.Thu) == DaysBitMask.Thu)
    test = true;
if ((reHydratedMask & DaysBitMask.Wed) != DaysBitMask.Wed)
    test = true;
Nick Wright
fuente
Hice algo similar, pero al definir la máscara hice Mon = Math.Pow (2, 0), Tuesday = Math.Pow (2, 1), Wed = Math.Pow (2, 2), etc. así que la posición del bit es un poco más obvio para aquellos que no están acostumbrados a la conversión de binario a decimal. Blindy también es bueno, ya que se convierte en un resultado booleano al cambiar el bit enmascarado.
Incendiario analógico
7

Para combinar máscaras de bits, desea utilizar bit a bit o . En el caso trivial en el que cada valor que combina tiene exactamente 1 bit activado (como su ejemplo), es equivalente a sumarlos. Si ha superpuestas trozos sin embargo, o 'ing ellos maneja el caso con gracia.

Para decodificar las máscaras de bits usted y su valor con una máscara, así:

if(val & (1<<1)) SusanIsOn();
if(val & (1<<2)) BobIsOn();
if(val & (1<<3)) KarenIsOn();
Blindy
fuente
1
No puede usar un número entero como booleano en C #.
Shadow
6

Manera fácil:

[Flags]
public enum MyFlags {
    None = 0,
    Susan = 1,
    Alice = 2,
    Bob = 4,
    Eve = 8
}

Para establecer las banderas, use el operador lógico "o" |:

MyFlags f = new MyFlags();
f = MyFlags.Alice | MyFlags.Bob;

Y para comprobar si se incluye una bandera, utilice HasFlag:

if(f.HasFlag(MyFlags.Alice)) { /* true */}
if(f.HasFlag(MyFlags.Eve)) { /* false */}
A-Sharabiani
fuente
Parece que toda esta información ya se ha proporcionado anteriormente. Si está proporcionando información nueva , debe marcarla claramente.
sonyisda1
1
En otras respuestas no se proporcionó un ejemplo simple del uso de HasFlag()y [Flags].
A-Sharabiani
0

Otra razón realmente buena para usar una máscara de bits frente a bools individuales es que, como desarrollador web, al integrar un sitio web a otro, con frecuencia necesitamos enviar parámetros o indicadores en la cadena de consulta. Siempre que todos sus indicadores sean binarios, es mucho más sencillo usar un solo valor como máscara de bits que enviar múltiples valores como bools. Sé que hay otras formas de enviar datos (GET, POST, etc.), pero un parámetro simple en la cadena de consulta suele ser suficiente para elementos no sensibles. Intente enviar 128 valores bool en una cadena de consulta para comunicarse con un sitio externo. Esto también brinda la capacidad adicional de no sobrepasar el límite de cadenas de consulta de URL en los navegadores.

Greg Osborne
fuente
No es realmente una respuesta a la pregunta del OP, debería haber sido un comentario.
sonyisda1