Enumerations, bitwise operators and flags
Ημερομηνία: 16/07/2017
Δεν έχετε συμπληρώσει όλα τα απαιτούμενα στοιχεία
Το email που δώσατε δεν είναι σωστό
Enumerations are a simple and efficient way to deal with a set of values. The most common way to use an enumeration is to use its values separately. There are however times when we want to use a combination of the enumeration's values. In that case we can use bitwise operators. To make things easier, we can also use flags.
Enumeration
Let's start using a simple enumeration example. Enumerations are structs consisting of a set of values the type can be assigned. So let's use an enumeration of payment methods. Here's the enumeration.
public enum PaymentMethods
{
None = 0,
Cash = 1,
Cheque = 2,
CreditCard = 3
}
We can now use this enumeration to connect PaymentMethod values with their integer representation. For example
int creditCard = 3;
PaymentMethods creditCardMethod = (PaymentMethods)creditCard;
or
int creditCard = (int)PaymentMethods.CreditCard;
It is quite easy to switch between the enumeration and its numeric equivalent and use it anyway we want. Either to set the value to a select item or to store it in a database table. This method seems to be ok for let's say an e-shop where the customer chooses a product and selects a method to pay. However, that wouldn't work in case of a crm system where the customers wished to have more than one way of payment. For example, some client might wish to choose as payment method Cheque or CreditCard.
Bitwise Operators
In order to choose multiple values rather than a single one, we can use bitwise operators. In other words we can use an enumeration's binary value instead of the integer representation. This is called Flag Enumeration, even though using the Flag keyword is not mandatory.
For example, in the PaymentMethods enumeration each value's binary representation would be
Name Integer Binary
None 0 000
Cash 1 001
Cheque 2 010
CreditCard 3 011
Now, we could change the enumeration's values so they were all represented by powers of 2.
public enum PaymentMethodsAdvanced
{
None = 0,
Cash = 1,
Cheque = 2,
CreditCard = 4
}
In that case the previous table would look like
Name Integer Binary
None 0 000
Cash 1 001
Cheque 2 010
CreditCard 4 100
It's pretty clear that all we have said so far concerning PaymentMethods applies to PaymentMethodsAdvanced as well. Now let's see what choosing a combination of these values would look like.
var cashOrCheque = PaymentMethodsAdvanced.Cash | PaymentMethodsAdvanced.Cheque;
var cashOrCreditCard = PaymentMethodsAdvanced.Cash | PaymentMethodsAdvanced.CreditCard;
Name Integer Binary
None 0 000
Cash 1 001
Cheque 2 010
CreditCard 4 100
cashOrCheque 3 011
cashOrCreditCard 5 101
This way we can represent multiple options by using an option that is created on the fly.
In order to check if a variable contains an option we can use the & operator.
int paymentMethodAdvancedIntValue = 3;
var paymentMethodAdvancedValue = (PaymentMethodsAdvanced)paymentMethodAdvancedIntValue;
bool isCash = (paymentMethodAdvancedValue & PaymentMethodsAdvanced.Cash) == PaymentMethodsAdvanced.Cash; //true
var cashOrCheque = PaymentMethodsAdvanced.Cash | PaymentMethodsAdvanced.Cheque;
bool isCashOrCheque = (paymentMethodAdvancedValue & cashOrCheque) == cashOrCheque; //true
Instead of using logical operators we can use the HasFlag method. HasFlag works in a similar way but it makes things easier to read and use.
int paymentMethodAdvancedIntValue = 3;
var paymentMethodAdvancedValue = (PaymentMethodsAdvanced)paymentMethodAdvancedIntValue;
bool isCash = paymentMethodAdvancedValue.HasFlag(PaymentMethodsAdvanced.Cash); //true
var cashOrCheque = PaymentMethodsAdvanced.Cash | PaymentMethodsAdvanced.Cheque;
bool isCashOrCheque = paymentMethodAdvancedValue.HasFlag(cashOrCheque); //true
var cashOrCreditCard = PaymentMethodsAdvanced.Cash | PaymentMethodsAdvanced.CreditCard;
bool isCashOrCreditCard = paymentMethodAdvancedValue.HasFlag(cashOrCreditCard); //false
One thing to keep in mind is how the 0 value works; the one called None in our example. Suppose we use HasFlag or the & operator (which actually end up in the same thing). Here's how things might get tricky.
var cashOrCheque = PaymentMethodsAdvanced.Cash | PaymentMethodsAdvanced.Cheque;
bool isNone = cashOrCheque.HasFlag(PaymentMethodsAdvanced.None);
isNone is true. Actually everything you compare to None returns true. Why would you say this is? It all ends up to this.
bool isNone = (cashOrCheque & PaymentMethodsAdvanced.None) == PaymentMethodsAdvanced.None;
3 & 0 == 0
This expression is always true regardless of the first value. So this is not the right way to tell if the value is None. To do so, we should do a direct comparison.
bool isNone = cashOrCheque == PaymentMethodsAdvanced.None;
One last thing: remember how we created our enumeration?
public enum PaymentMethodsAdvanced
{
None = 0,
Cash = 1,
Cheque= 2,
CreditCard = 4
}
The exact same thing can be done using bit shifting which can make things less complicated in case of long enumerations.
public enum PaymentMethodsAdvanced
{
None = 0,
Cash = 1 << 0,
Cheque= 1 << 1,
CreditCard = 1 << 2
}
Using Flags
It is a strange thing that many people tend to think that you need to use the Flags attribute in order to use bitwise operators. That is not true. We can use Flags however, to make things easier for us developers to debug or to display selected values.
To use Flags the only thing we need to do is to place the Flag keyword before the enumeration.
[Flags]
public enum PaymentMethodsFlags
{
None = 0,
Cash = 1,
Cheque = 2,
CreditCard = 4
}
So, if we had an enumeration with Flags as mentioned and another one without Flags, like this
public enum PaymentMethods
{
None = 0,
Cash = 1,
Cheque = 2,
CreditCard = 4
}
using the debugger we would get the following results
var cashOrCheque = PaymentMethods.Cash | PaymentMethods.Cheque; //Debugger says 3
var cashOrChequeFlags = PaymentMethodsFlags.Cash | PaymentMethodsFlags.Cheque; //Debugger says Cash | Cheque
Similarly
string cashOrCheque = (PaymentMethods.Cash | PaymentMethods.Cheque).ToString(); //Equals to "3"
string cashOrChequeFlags = (PaymentMethodsFlags.Cash | PaymentMethodsFlags.Cheque).ToString(); //Equals to "Cash, Cheque"
Summary
We can use bitwise operators to combine multiple values from one single enumeration. The enumeration needs to contain powers of two as values. To compare such values we can use logical operators or the HasFlag method. In addition, we can use the Flags attribute to make debugging or parsing enumeration values easier.
Πίσω στο BlogΠροηγούμενο