Έχουν δημοσιευτεί Τετάρτη, 19 Ιουλίου 2017 9:51 πμ από το μέλος k badas

Enumerations, bitwise operators and flags

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 in order 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.

Reference – DotNetHints

Share


Σχόλια:

Χωρίς Σχόλια