One of the most unused features in .NET I have come across is the ORing and ANDing of enum values.
I've been finding myself explaining this technique several times in the last week, so I figured I'd write it all down here.
How many times have you had the need to store non-exclusive boolean values and had to write a tome of .isXXX properties?
For example, a user rights object (isAdmin, isUser, isPowerUser, etc).
Creating and checking a list of related properties, and taking actions on the myriad of combinations quickly becomes tedious and results in unmaintainable code.
This is where ORing and ANDing come in handy. These are bit wise operators, and so operate on the binary representation of numbers.
If you are unfamiliar with binary numbers, read this next section, otherwise skip it.
Binary
Binary is read right to left, with each column being a power of 2 of the previous column. The first column is the "ones" column, the second is the "twos" column, the third is the "fours", the fourth is the "eights" etc.
You add the values of the individual columns together for the resultant number. Here are some examples:
00000000 = 0
00000001 = 1
00000010 = 2
00000011 = 3
00001001 = 9
The above are 8 bit integers (8 columns) and thus can store a max value of 255 (11111111)
Bitwise OperatorsThere are several bit operators (XOR, NOR, etc.) and languages refer to them differently , but we only need 2 for this VB.NET experiment; AND and OR. When you AND or OR at the bit level, we can use a long hand notataion that looks similar to what we learned in grade school, but it operates a little differently because results are all boolean, True (1) or False (0).
AND is true when both bits are true:
1 AND 1 = 1
1 AND 0 = 0
0 AND 0 = 0
OR is true if either is true:
1 OR 1 = 1
1 OR 0 = 1
0 OR 0 = 0
Lets AND together two 4 bit numbers:
0101 (5)
0011 (3)
---- AND
0001 (1)
Now we'll OR the same 2 numbers.
0101 (5)
0011 (3)
---- OR
0111 (7)
Since all the values in an enum are represented by integers, we can use these techniques to create long lists of non-exclusive boolean values, and store them in one number. All we have to do is treat the individual bits as true/false values.
To make sure we can do this, all the values in the enum need to be powers of 2.
Heres an enum we can use to determine what toppings to put on a pizza:
Public Enum Toppings Pepperoni = 1 Mushrooms = 2 Onions = 4 Anchovies = 8 Peppers = 16 Pineapple = 32 End Enum
|
If I had a function called MakePizza that took a Toppings enum as an argument:
MakePizza(UseToppings as Toppings)
|
we can OR all the values we need together:
Dim MyToppings as Toppings MyToppings = Mushrooms OR Onions OR Peppers
|
The value stored in the enum is now 22:
00000010 (Mushrooms - 2)
00000100 (Onions - 4)
00010000 (Peppers - 16)
-------- OR
00010110 (22)
Inside our MakePizza function, we will need to find out the distinct values used to create UseToppings (22)
To do this we will need to AND the UseToppings variable with the values we are testing for.
Any non zero integer will represent a true result.
For example, lets test if Pepperoni was passed in.
If UseToppings AND Toppings.Pepperoni Then 'Add pepperonis End If
|
This evaluates to false (0):
00010110 (UseToppings - 22)
00000001 (Pepperoni - 1)
-------- AND
00000000 (0)
Now lets test for Onions:
If UseToppings AND Toppings.Onions Then
'Add onions
End If
|
This evaluates to true (greater than 0):
00010110 (UseToppings - 22)
00000100 (Onions - 4)
-------- AND
00000100 (4)
We can also eliminate much of the storage associated with representing this object in a database or memory.
Instead of having true/false colums named Pepperoni, Onions, Mushrooms, etc., we can store one number 22.
Although the pizza example is a bit ludicrous, this technique comes in real handy when storing a user's application rights.
You can build an enum of rights values (Edit, ReadOnly, Admin, etc.) and store them in one database column or session variable.
As a side note, the .NET spec says enums to be used this way should carry the < FLAGS() > attribute, however the complier and runtime won't stop you from doing bit operations either way.