Afinal, o que é o atributo Flags em C#?
Um dia desses, conversando com um colega de trabalho que estava me pedindo uma opinião sobre como resolver um problema específico, disse para usar Enum como Flags para resolver o seu problema (basicamente era setar um conjunto de opções cumulativas para configurar uma nova instância de uma classe. Ao sugerir isso, ele disse que não fazia idéia o que eram Flags e como usá-las. Eis que descubro que o que achamos “simples” e que por causa disso “todos” conhecem e sabem como usar, é uma mentira. Com isso, além de explicar em detalhes e vislumbrar o quão interessante ele achou disso, decidi compartilhar um pouco mais sobre o assunto aqui com vocês :)
Flags
O atributo Flags deve ser usado sempre que o enumerador representar uma “coleção de valores possíveis e possivelmente cumulativos”, em vez de um único valor. Essas “coleções” são frequentemente usadas com operadores bit-a-bit, por exemplo:
Repare que sem usar Flags, com um operador bit-a-bit (| = binary or), ele consegue fazer o “cálculo” corretamente, mas o resultado sem Flags é um “número” inválido, que não representa nenhuma enumerador. Já com o uso de Flags, o método ToString() é capaz de plotar todos os valores combinados anteriormente.
O uso de [Flags] requer obrigatoriamente que você atribua os valores de cada enumerador de uma forma que cada um represente unicamente um bit. Para isso você deve usar números do resultado exponencial da base binária. Ou seja, use 2 elevado a X, onde X é a ordem do enumerador.
Enumerador 0 = 2⁰ = 1
Enumerador 1= 2¹ = 2
Enumerador 2= 2² = 4
Enumerador 3= 2³ = 8
Enumerador 4= 2⁴= 16
E assim por diante. O uso de Flags infelizmente (ainda) não coloca esses valores por padrão nos enumeradores.
Para verificar se um valor está contindo em um enumerador com Flags, você pode fazer assim:
Veja um exemplo um pouco mais “real” com e sem o uso de Flags:
Percebemos que a complexidade pra gerir opções cumulativas sem o uso de Flags é maior. Imagine os ifs e as condições necessários para resolver um enumerador com 5, 10, 20 opçoes diferentes.
O valor “0”
Você pode usar um elemento com o valor zero [ex. None = 0]. O problema é que em uma operação bit a bit para testar uma Flag o resultado sempre será zero, logo, sempre será true, dado que se o resultado é a comparação do valor de entrar (None = 0) com o resultado da operação bit-a-bit (sempre será 0).
Quando usamos o valor “0”, queremos dizer que nenhuma outra opção foi selecionada, seria um valor não cumulativo. Para comparar e descobrir se a sua variável não contém nenhuma opção, você pode simplesmente comparar com o próprio enumerador titular do valor zero, ao invés de usar o método HasFlag().
Por debaixo dos panos
Se pararmos pra analisar, basicamente é como se estivessemos usando os valores não indicados (por exemplo, 3, 5, 6, 7, etc) para criar enumeradores com todas as combinações possíveis. Isso torna a implementação muito mais fácil quando a idéia do enumerador é ser “cumulativo”.
Nos bastidores, seus valores de enumeração são assim em zeros e binários:
Para juntar esses valores usamos o operador “or” (|), feito bit-a-bit. Se pelo menos uma das colunas for true (nesse caso 1) o resultado é true (1).
Quando tentado validar variável se ela contém uma ou mais Flags usando HasFlag() estamos aplicando o operador “and” (&).
Tamanho do Enumerador
Um enumerador é uma herança do Int32. Como o uso de Flags ocupa 1 bit por valor no enumerador, poderiamos ter no máximo 32 valores diferentes (com exceção do None = 0). Para aumentar ou diminuir o tamanho de um enumerador podemos alterar o seu tipo base para long (e ter 64 bits / valores distintos) ou para short (e ter apenas 16 bits / valores).
Veja o exemplo em código, que explica mais do que palavras:
E se eu precisar de mais de 64 valores diferentes? Bom, primeiramente você deve repensar se usar um enumerador / flags realmente será a melhor opção. Não costumo (e não gosto de) usar enumeradores para coisas tão grandes assim. Mas, se realmente precisar, será necessário uma implementação para utilizar as características cumulativas da Flag. Veja um exemplo:
O assunto Flags apesar de ser simples, pode te ajudar em diversas implementações do dia-a-dia. O estudo a fundo desse assunto pode te ajudar mais ainda a fortalecer a sua base conceitual sobre computação, números binários, operações bit-a-bit, or, and, xor, not, etc.
Hoje percebo que cada vez mais os programadores estão conhecendo somente os comandos da linguagem ou muitas “tecnologias”, no modo “como usar”, mas não exatamente como as coisas funcionam por trás, o que prejudica demais o entendimento do todo. E isso envolve diversos assuntos, como usar Tasks mas não saber como funciona escalonamento de processos na CPU ou simplesmente movimentar objetos primitivos, complexos, por referência, etc, sem saber como funciona o endereçamento e alocação de memória.
Mas esses pontos são assuntos para uma outra postagem 😙