Operações mais comuns de C # bit a bit em enumerações

201

Por toda a minha vida, não me lembro como definir, excluir, alternar ou testar um pouco em um campo de bits. Ou não tenho certeza ou os confundo porque raramente preciso deles. Portanto, seria bom ter uma "folha de dicas".

Por exemplo:

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

ou

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

Você pode dar exemplos de todas as outras operações comuns, de preferência na sintaxe C # usando uma enumeração [Flags]?

steffenj
fonte
5
Isso já foi respondido antes aqui
Greg Rogers
7
pena que o link não apareça nas dicas da pergunta para este tópico.
cori
10
Porém, essa pergunta está marcada para c / c ++, portanto, alguém que procura informações sobre C # provavelmente não procuraria lá, mesmo que a sintaxe pareça ser a mesma.
Adam Lassek 18/09/08
Eu não estou ciente de uma maneira menos detalhada para fazer o teste bit
Andy Johnson
2
@ Andy, agora há uma API para o teste de bits no .NET 4.
Drew Noakes

Respostas:

288

Eu trabalhei mais nessas extensões - Você pode encontrar o código aqui

Eu escrevi alguns métodos de extensão que estendem o System.Enum que eu uso frequentemente ... Não estou afirmando que são à prova de balas, mas eles ajudaram ... Comentários removidos ...

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

    }
}

Então eles são usados ​​como o seguinte

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
fonte
1
Eu também achei isso útil - Alguma idéia de como posso modificá-lo para que funcione em qualquer tipo subjacente?
Charlie Salts
7
Essas extensões tornaram meu dia, minha semana, meu mês e, possivelmente, meu ano.
ThaBadDawg 3/03
Obrigado! Todos: verifique a atualização à qual o Hugoware está vinculado.
Helge Klein
Um conjunto muito agradável de extensões. É uma pena que eles exijam boxe, embora eu não consiga pensar em uma alternativa que não use boxe e isso seja sucinto. Até o novo HasFlagmétodo Enumrequer boxe.
Drew Noakes
4
@Drew: Veja code.google.com/p/unconstrained-melody para uma forma de evitar boxe :)
Jon Skeet
109

No .NET 4, agora você pode escrever:

flags.HasFlag(FlagsEnum.Bit4)
Drew Noakes
fonte
4
+1 por apontar isso, embora FlagsEnumseja um nome feio. :)
Jim Schubert
2
@ Jim, talvez. É apenas um nome de amostra, conforme usado na pergunta original, para que você possa alterá-lo em seu código.
de Drew Noakes
14
Eu sei! Mas os nomes feios são como IE6 e provavelmente nunca vai embora :(
Jim Schubert
5
@ JimSchubert, novamente, acabei de reproduzir o nome do tipo da pergunta original para não confundir o problema. As Diretrizes de nomeação de tipos de enumeração do .NET indicam que todas as [Flags]enumerações devem ter nomes pluralizados, portanto, o nome FlagsEnumtem problemas ainda mais sérios que a feiura.
de Drew Noakes
1
Também recomendo as Diretrizes de design da estrutura: convenções, expressões idiomáticas e padrões para bibliotecas .NET reutilizáveis . É um pouco caro comprar, mas acredito que o Safari Online e o Books24x7 o oferecem para assinantes.
Jim Schubert
89

O idioma é usar o operador bit a bit ou igual para definir bits:

flags |= 0x04;

Para limpar um pouco, o idioma é usar bit a bit e com negação:

flags &= ~0x04;

Às vezes, você tem um deslocamento que identifica seu bit e, em seguida, o idioma é usá-lo combinado com o deslocamento para a esquerda:

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

@Desenhou

Observe que, exceto nos casos mais simples, o Enum.HasFlag acarreta uma penalidade de alto desempenho em comparação à gravação manual do código. Considere o seguinte 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();
    }
}

Mais de 10 milhões de iterações, o método de extensão HasFlags ocupa 4793 ms, em comparação com os 27 ms da implementação padrão em bits.

Chuck Dee
fonte
10
Embora certamente interessante e bom ressaltar. Você precisa considerar o uso. De acordo com isso, se você não estiver realizando algumas centenas de milhares de operações ou mais, provavelmente nem perceberá isso.
Joshua Hayes
7
o HasFlag método envolve boxe / unboxing, o que explica essa diferença. Mas o custo é tão trivial (0,4µs) que, a menos que você esteja em um loop apertado, eu aceitaria a chamada declarativa da API mais legível (e menos provável de buggy) a qualquer dia.
de Drew Noakes
8
Dependendo do uso, pode ser um problema. E como eu trabalho com carregadeiras um pouco, achei bom ressaltar.
Chuck Dee
11

Infelizmente, as operações de enumeração de sinalizador do .NET são bastante limitadas. Na maioria das vezes, os usuários ficam com a lógica de operação bit a bit.

No .NET 4, HasFlagfoi adicionado o método Enumque ajuda a simplificar o código do usuário, mas infelizmente há muitos problemas com ele.

  1. HasFlag não é seguro quanto ao tipo, pois aceita qualquer tipo de argumento de valor de enumeração, não apenas o tipo de enumeração especificado.
  2. HasFlagé ambíguo se verifica se o valor possui todos ou alguns dos sinalizadores fornecidos pelo argumento de valor da enumeração. É tudo a propósito.
  3. HasFlag é bastante lento, pois requer boxe, o que causa alocações e, portanto, mais coleta de lixo.

Em parte devido ao suporte limitado do .NET para enumerações de flag, escrevi a biblioteca OSS Enums.NET, que aborda cada um desses problemas e facilita muito o tratamento de enumerações de flag.

Abaixo estão algumas das operações que ele fornece, juntamente com suas implementações equivalentes, usando apenas a estrutura .NET.

Combinar sinalizadores

.INTERNET             flags | otherFlags

Enums.NET flags.CombineFlags(otherFlags)


Remover sinalizadores

.INTERNET             flags & ~otherFlags

Enums.NET flags.RemoveFlags(otherFlags)


Sinalizadores comuns

.INTERNET             flags & otherFlags

Enums.NET flags.CommonFlags(otherFlags)


Alternar sinalizadores

.INTERNET             flags ^ otherFlags

Enums.NET flags.ToggleFlags(otherFlags)


Tem todas as bandeiras

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

Enums.NET flags.HasAllFlags(otherFlags)


Tem sinalizadores

.INTERNET             (flags & otherFlags) != 0

Enums.NET flags.HasAnyFlags(otherFlags)


Obter sinalizadores

.INTERNET

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


Estou tentando incorporar essas melhorias no .NET Core e, eventualmente, no .NET Framework completo. Você pode conferir minha proposta aqui .

TylerBrinkley
fonte
7

Sintaxe C ++, assumindo que o bit 0 é LSB, assumindo que os sinalizadores não tenham assinatura longa:

Verifique se definido:

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

Verifique se não estiver definido:

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

Conjunto:

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

Claro:

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

Alternancia:

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

Para obter o melhor desempenho e zero lixo, use o seguinte:

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
fonte
2

Para testar um pouco, faça o seguinte: (supondo que sinalizadores seja um número de 32 bits)

Bit de teste:

if((flags & 0x08) == 0x08)
(Se o bit 4 estiver definido, será verdadeiro) Alternar para trás (1 - 0 ou 0 - 1):
flags = flags ^ 0x08;
Redefina o Bit 4 para Zero:
flags = flags & 0xFFFFFF7F;

Nashirak
fonte
2
-1, pois isso nem se importa com enumerações? Além disso, a mão-de codificação dos valores é frágil ... Eu, pelo menos, escrita ~0x08em vez de 0xFFFFFFF7... (a máscara real para 0x8)
Ben Mosher
1
No começo, eu pensava que o -1 de Ben era duro, mas o uso de "0xFFFFFF7F" torna esse um exemplo especialmente ruim.
Home
2

Isso foi inspirado pelo uso de Sets como indexadores no Delphi, quando:

/// 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
fonte
2
Isso ainda não é compilado se BindingFlags for byte enum: this.flags & = ~ index;
amuliar
0

As operações C ++ são: & | ^ ~ (para e, ou, xor e não operações bit a bit). Também são interessantes >> e <<, que são operações de deslocamento de bits.

Portanto, para testar um bit sendo definido em um sinalizador, você usaria: se (flags & 8) // testes o bit 4 foi definido

workmad3
fonte
8
Questão refere-se c #, não c ++
Andy Johnson
3
Por outro lado, C # usa os mesmos operadores: msdn.microsoft.com/en-us/library/6a71f45d.aspx
ToolmakerSteve
3
Em defesa da @ workmad3, as etiquetas originais teve C e C ++
pqsk