Como verificar se algum sinalizador de uma combinação de sinalizador está definido?

180

Digamos que eu tenho esse enum:

[Flags]
enum Letters
{
     A = 1,
     B = 2,
     C = 4,
     AB = A | B,
     All = A | B | C,
}

Para verificar se, por exemplo, ABestá definido, posso fazer o seguinte:

if((letter & Letters.AB) == Letters.AB)

Existe uma maneira mais simples de verificar se algum dos sinalizadores de uma constante de sinalizador combinado está definido como o seguinte?

if((letter & Letters.A) == Letters.A || (letter & Letters.B) == Letters.B)

Você poderia, por exemplo, trocar o &item com algo?

Não é muito estável quando se trata de coisas binárias como esta ...

Svish
fonte
Todos não deveriam ler 'All = A | B C '?
stevehipwell
4
AB C é equivalente a A | B C porque AB foi definido como A | B antes.
Daniel Brückner
1
@ Daniel Brückner - É equivalente, mas é menos legível. Especialmente se o enum foi expandido.
stevehipwell
Verdade. Eu posso alterá-lo para uma melhor leitura.
Svish

Respostas:

145

Se você quiser saber se a letra tem alguma das letras em AB, use o operador AND &. Algo como:

if ((letter & Letters.AB) != 0)
{
    // Some flag (A,B or both) is enabled
}
else
{
    // None of them are enabled
}
yeyeyerman
fonte
2
Tanto quanto eu posso ver, isso faz o trabalho. E teve os comentários mais claros. Não compila embora sem parênteses letter & Letters.AB. Editou isso lá.
Svish
Além disso, se eu apresentei um Letters.None, presumo que você possa trocá-lo com 0um visual menos comparável com o número mágico
Svish
Claro. Mas não acho que a comparação AND com 0 possa ser pensada estritamente como um número mágico.
yeyeyerman
9
também stackoverflow.com/questions/40211/how-to-compare-flags-in-c é uma resposta recomendada, pois verifica o item em questão em vez de verificar se é igual a 0
dan richardson
@danrichardson O problema com a verificação do item exato é que ele elimina o caso quando uma parte do valor composto é definida (A ou B), o que não é o que o OP deseja.
Tom Lint
181

No .NET 4, você pode usar o método Enum.HasFlag :

using System;

[Flags] public enum Pet {
   None = 0,
   Dog = 1,
   Cat = 2,
   Bird = 4,
   Rabbit = 8,
   Other = 16
}

public class Example
{
   public static void Main()
   {
      // Define three families: one without pets, one with dog + cat and one with a dog only
      Pet[] petsInFamilies = { Pet.None, Pet.Dog | Pet.Cat, Pet.Dog };
      int familiesWithoutPets = 0;
      int familiesWithDog = 0;

      foreach (Pet petsInFamily in petsInFamilies)
      {
         // Count families that have no pets. 
         if (petsInFamily.Equals(Pet.None))
            familiesWithoutPets++;
         // Of families with pets, count families that have a dog. 
         else if (petsInFamily.HasFlag(Pet.Dog))
            familiesWithDog++;
      }
      Console.WriteLine("{0} of {1} families in the sample have no pets.", 
                        familiesWithoutPets, petsInFamilies.Length);   
      Console.WriteLine("{0} of {1} families in the sample have a dog.", 
                        familiesWithDog, petsInFamilies.Length);   
   }
}

O exemplo exibe a seguinte saída:

//       1 of 3 families in the sample have no pets. 
//       2 of 3 families in the sample have a dog.
Chuck Kasabula
fonte
14
Isso não aborda a questão do OP. Você ainda deve && várias operações HasFlag para determinar se algum sinalizador está definido. Portanto, a questão é se petsInFamilytem um Pet.Dog || Pet.Cat?
GoClimbColorado 04/04
1
Ver resposta clara do Sr. Skeet ... HasFlags múltipla
GoClimbColorado
59

Eu uso métodos de extensão para escrever coisas assim:

if (letter.IsFlagSet(Letter.AB))
    ...

Aqui está o código:

public static class EnumExtensions
{
    private static void CheckIsEnum<T>(bool withFlags)
    {
        if (!typeof(T).IsEnum)
            throw new ArgumentException(string.Format("Type '{0}' is not an enum", typeof(T).FullName));
        if (withFlags && !Attribute.IsDefined(typeof(T), typeof(FlagsAttribute)))
            throw new ArgumentException(string.Format("Type '{0}' doesn't have the 'Flags' attribute", typeof(T).FullName));
    }

    public static bool IsFlagSet<T>(this T value, T flag) where T : struct
    {
        CheckIsEnum<T>(true);
        long lValue = Convert.ToInt64(value);
        long lFlag = Convert.ToInt64(flag);
        return (lValue & lFlag) != 0;
    }

    public static IEnumerable<T> GetFlags<T>(this T value) where T : struct
    {
        CheckIsEnum<T>(true);
        foreach (T flag in Enum.GetValues(typeof(T)).Cast<T>())
        {
            if (value.IsFlagSet(flag))
                yield return flag;
        }
    }

    public static T SetFlags<T>(this T value, T flags, bool on) where T : struct
    {
        CheckIsEnum<T>(true);
        long lValue = Convert.ToInt64(value);
        long lFlag = Convert.ToInt64(flags);
        if (on)
        {
            lValue |= lFlag;
        }
        else
        {
            lValue &= (~lFlag);
        }
        return (T)Enum.ToObject(typeof(T), lValue);
    }

    public static T SetFlags<T>(this T value, T flags) where T : struct
    {
        return value.SetFlags(flags, true);
    }

    public static T ClearFlags<T>(this T value, T flags) where T : struct
    {
        return value.SetFlags(flags, false);
    }

    public static T CombineFlags<T>(this IEnumerable<T> flags) where T : struct
    {
        CheckIsEnum<T>(true);
        long lValue = 0;
        foreach (T flag in flags)
        {
            long lFlag = Convert.ToInt64(flag);
            lValue |= lFlag;
        }
        return (T)Enum.ToObject(typeof(T), lValue);
    }

    public static string GetDescription<T>(this T value) where T : struct
    {
        CheckIsEnum<T>(false);
        string name = Enum.GetName(typeof(T), value);
        if (name != null)
        {
            FieldInfo field = typeof(T).GetField(name);
            if (field != null)
            {
                DescriptionAttribute attr = Attribute.GetCustomAttribute(field, typeof(DescriptionAttribute)) as DescriptionAttribute;
                if (attr != null)
                {
                    return attr.Description;
                }
            }
        }
        return null;
    }
}
Thomas Levesque
fonte
1
Você poderia torná-lo um pouco mais apertado assim: where T : struct, IConvertible. Código excelente caso contrário!
Hamish Grubijan
@HamishGrubijan, bom ponto ... e enums também implementam IFormattable e IComparable. No entanto, todos os tipos numéricos implementar essas interfaces também, por isso não é suficiente para excluí-los
Thomas Levesque
Obrigado por compartilhar, mas você nem sempre precisa verificar a enumeração. IsFlagSet(this Enum value, Enum flag)é suficiente.
djmj
34

Há o método HasFlag no .NET 4 ou superior.

if(letter.HasFlag(Letters.AB))
{
}
Artru
fonte
26

Se você pode usar o .NET 4 ou superior, use o método HasFlag ()

exemplos

letter.HasFlag(Letters.A | Letters.B) // both A and B must be set

igual a

letter.HasFlag(Letters.AB)
Luka
fonte
Você tem certeza de bitwise ORque "ambos devem ser definidos" e não nenhum?
Parênteses
1
bitwise ORcombinaria os valores, então 1000 | 0010 torna-se 1010, ou ambos set
Armando
13

Se isso realmente te incomoda, você pode escrever uma função assim:

public bool IsSet(Letters value, Letters flag)
{
    return (value & flag) == flag;
}

if (IsSet(letter, Letters.A))
{
   // ...
}

// If you want to check if BOTH Letters.A and Letters.B are set:
if (IsSet(letter, Letters.A & Letters.B))
{
   // ...
}

// If you want an OR, I'm afraid you will have to be more verbose:
if (IsSet(letter, Letters.A) || IsSet(letter, Letters.B))
{
   // ...
}
Tamas Czinege
fonte
1
A linha return (value & flag) == flag;não compila: "Operador 'e' não podem ser aplicados a operandos do tipo 'T' e 'T'" .
Fredrik Mörk
1
temor: A questão não era sobre operações binárias, mas sim sobre a simplificação da sintaxe de operações relacionadas a máscaras de bits em C #. Já existem muitas excelentes perguntas e respostas relacionadas à operação binária no stackoverflow, não há necessidade de repassá-las em todos os lugares.
Tamas Czinege 27/08/09
Eu recomendo que aqueles que não estão familiarizados com operações binárias se familiarizem, pois o andaime para escondê-lo acima realmente torna as coisas muito menos legíveis na minha opinião. É claro que minha solução "bruta" abaixo atualmente não está indo tão bem em comparação com a pontuação dessa solução, então as pessoas estão votando em suas preferências e eu tenho que respeitar isso ;-)
Será
10

Para verificar se, por exemplo, AB está definido, posso fazer o seguinte:

if ((letter & Letters.AB) == Letters.AB)

Existe uma maneira mais simples de verificar se algum dos sinalizadores de uma constante de sinalizador combinado está definido como o seguinte?

Isso verifica que ambos A e B são definidas e ignora se qualquer outras bandeiras são set.

if((letter & Letters.A) == Letters.A || (letter & Letters.B) == Letters.B)

Isso verifica se A ou B está definido e ignora se outros sinalizadores estão definidos ou não.

Isso pode ser simplificado para:

if(letter & Letters.AB)

Aqui está o C para operações binárias; deve ser simples aplicá-lo ao c #:

enum {
     A = 1,
     B = 2,
     C = 4,
     AB = A | B,
     All = AB | C,
};

int flags = A|C;

bool anything_and_a = flags & A;

bool only_a = (flags == A);

bool a_and_or_c_and_anything_else = flags & (A|C);

bool both_ac_and_anything_else = (flags & (A|C)) == (A|C);

bool only_a_and_c = (flags == (A|C));

Aliás, a nomeação da variável no exemplo da pergunta é a singular 'letra', o que pode implicar que ela represente apenas uma única letra; o código de exemplo deixa claro que é um conjunto de letras possíveis e que vários valores são permitidos; portanto, considere renomear a variável 'letters'.

Vai
fonte
Não seria anything_and_a, a_and_or_c_and_anything_elsee both_ac_and_anything_elsesempre será verdade? ou estou faltando alguma coisa aqui?
Svish
Nesse caso, você pode ver para que sinalizadores foram inicializados. No entanto, se os sinalizadores não contiverem A, (sinalizadores & A) seria 0, o que é falso. both_ac_and_anything_else garante que A e C estejam configurados, mas ignora quaisquer outros sinalizadores que também estão configurados (por exemplo, é verdadeiro se B está configurado ou não).
Will
Hm, alguns desses acabam como números e não são booleanos em C #. Como você os converteria em booleano?
Svish
Não é implicitamente convertido para você? Zero é equivalente a 'false', e todos os outros valores são 'true'.
Will
4

E se

if ((letter & Letters.AB) > 0)

?

Jakob Christensen
fonte
Sim! Isso filtrará os valores A e B e ignorará se C estiver incluído. Portanto, se for> 0, também será A ou B ou AB.
temor
3
Isso não funciona 100% com valores assinados. ! = 0 é melhor que> 0 por esse motivo.
stevehipwell
4

Eu criei um método de extensão simples que não precisa checar os Enumtipos:

public static bool HasAnyFlag(this Enum value, Enum flags)
{
    return
        value != null && ((Convert.ToInt32(value) & Convert.ToInt32(flags)) != 0);
}

Também funciona em enumerações anuláveis. O HasFlagmétodo padrão não, então eu criei uma extensão para cobrir isso também.

public static bool HasFlag(this Enum value, Enum flags)
{
    int f = Convert.ToInt32(flags);

    return
        value != null && ((Convert.ToInt32(value) & f) == f);
}

Um teste simples:

[Flags]
enum Option
{
    None = 0x00,
    One = 0x01,
    Two = 0x02,
    Three = One | Two,
    Four = 0x04
}

[TestMethod]
public void HasAnyFlag()
{
    Option o1 = Option.One;
    Assert.AreEqual(true, o1.HasAnyFlag(Option.Three));
    Assert.AreEqual(false, o1.HasFlag(Option.Three));

    o1 |= Option.Two;
    Assert.AreEqual(true, o1.HasAnyFlag(Option.Three));
    Assert.AreEqual(true, o1.HasFlag(Option.Three));
}

[TestMethod]
public void HasAnyFlag_NullableEnum()
{
    Option? o1 = Option.One;
    Assert.AreEqual(true, o1.HasAnyFlag(Option.Three));
    Assert.AreEqual(false, o1.HasFlag(Option.Three));

    o1 |= Option.Two;
    Assert.AreEqual(true, o1.HasAnyFlag(Option.Three));
    Assert.AreEqual(true, o1.HasFlag(Option.Three));
}

Aproveitar!

Henk van Boeijen
fonte
4

Existem muitas respostas aqui, mas acho que a maneira mais idiomática de fazer isso com Flags seria Letters.AB.HasFlag (letter) ou (Letters.A | Letters.B) .HasFlag (letter) se você não já tem Letters.AB. letter.HasFlag (Letters.AB) só funciona se tiver os dois.

Novaterata
fonte
3

Isso funcionaria para você?

if ((letter & (Letters.A | Letters.B)) != 0)

Saudações,

Sebastiaan

Sebastiaan M
fonte
1

Você pode usar este método de extensão em enum, para qualquer tipo de enumeração:

public static bool IsSingle(this Enum value)
{
    var items = Enum.GetValues(value.GetType());
    var counter = 0;
    foreach (var item in items)
    {
        if (value.HasFlag((Enum)item))
        {
            counter++;
        }
        if (counter > 1)
        {
            return false;
        }
    }
    return true;
}
masehhat
fonte
0
if((int)letter != 0) { }
Lee
fonte
Você pode o mesmo erro que eu fiz - ele quer verificar se A ou B está definido, mas ignoram C.
Daniel Brückner
Você não precisa o elenco se você está verificando a enumeração contra a 0.
stevehipwell
Isso verificaria se algum deles estava definido, não se algum enum combinado foi definido.
Svish
0

Você pode apenas verificar se o valor não é zero.

if ((Int32)(letter & Letters.AB) != 0) { }

Mas consideraria uma solução melhor introduzir um novo valor de enumeração com o valor zero e comparar novamente esse valor de enumeração (se possível, porque você deve poder modificar a enumeração).

[Flags]
enum Letters
{
    None = 0,
    A    = 1,
    B    = 2,
    C    = 4,
    AB   =  A | B,
    All  = AB | C
}

if (letter != Letters.None) { }

ATUALIZAR

Não entendi bem a pergunta - consertei a primeira sugestão e simplesmente ignore a segunda.

Daniel Brückner
fonte
Você não precisa o elenco se você está verificando a enumeração contra a 0.
stevehipwell
0

Existem duas abordagens que eu posso ver que funcionariam para verificar se algum bit está sendo definido.

Abordagem A

if (letter != 0)
{
}

Isso funciona desde que você não se importe de verificar todos os bits, incluindo os não definidos também!

Abordagem B

if ((letter & Letters.All) != 0)
{
}

Isso verifica apenas os bits definidos, desde que Letters.All represente todos os bits possíveis.

Para bits específicos (um ou mais conjuntos), use o Aproach B substituindo Letters.All pelos bits que você deseja verificar (veja abaixo).

if ((letter & Letters.AB) != 0)
{
}
stevehipwell
fonte
Você pode o mesmo erro que eu fiz - ele quer verificar se A ou B está definido, mas ignoram C.
Daniel Brückner
-1

Desculpe, mas eu vou mostrá-lo em VB :)

   <Flags()> Public Enum Cnt As Integer
        None = 0
        One = 1
        Two = 2
        Three = 4
        Four = 8    
    End Enum

    Sub Test()
    Dim CntValue As New Cnt
    CntValue += Cnt.One
    CntValue += Cnt.Three
    Console.WriteLine(CntValue)
    End Sub

CntValue = 5 Portanto, o enum contém 1 + 4

Wiroko
fonte