Thursday, August 18, 2011

Using the Flags attribute with an Enumeration

I was recently tasked with adding an attribute to an object.  This object persists in the database and I assumed that adding an attribute meant I would add a new property to the class, add a new column to the database and tie it all together.

Well, that wasn't the case.  The implementation that was already in place used an enumeration for the collection of possible attributes.  That enumeration allowed more then one value to be stored.  The enumeration was then converted into an integer as it was stored in the database.  All I had to do to add the new attribute was add an additional value to the enumeration.

This served as yet another epiphany on how something works and then the realization that I have seen and used this before in many places already. What I am talking about is using an enumeration of values as a bitmask.

Now the trick if you want to call it that is each numeric value of the enumeration was 2 multiplied by the preceding value (except for zero and one) and the enumeration has a Flags attribute.  Here is some example code:

[Flags]
public enum Colors : int
{
    None = 0,
    Red = 1,
    Blue = 2,
    Yellow = 4,
    Green = 8,
    White = 16
}

Since the values of the enumeration above are actually numeric they are represented by the following table:

Color Number 32 Bit Definition
None 0 00000000000000000000000000000000
Red 1

00000000000000000000000000000001

Blue 2

00000000000000000000000000000010

Yellow 4

00000000000000000000000000000100

Green 8

00000000000000000000000000001000

White 16

00000000000000000000000000010000

Since the enumeration is of type integer it will be stored as a 32 bit number. Since the integers are 32 bit each bit represents an enumeration value so the enumeration can only have 32 items.  If I wanted more then 32 items in the enumeration I could change the type of the enumeration to long.  This would store the value as a 64 bit number therefore giving you a capacity of 64 items in the enumeration.

Here is some example code showing how to add and remove colors:

Colors myColors = Colors.None;

//lets add some colors
myColors = myColors | Colors.Red;
myColors = myColors | Colors.Green;
myColors = myColors | Colors.Blue;
myColors = myColors | Colors.White;

//lets remove a color
myColors = myColors ^ Colors.White;

The table below represents the value of the myColors variable at each addition and removal of a color.

Colors Number 32 Bit Definition
None 0 00000000000000000000000000000000
Red (1) 1

00000000000000000000000000000001

Red (1), Green (8)   1+8=9 9

00000000000000000000000000001001

Red (1), Green (8), Blue (2)  1+8+2=11 11 00000000000000000000000000001011
Red (1), Green (8), Blue (2), White (16)  1+8+2+16=27 27 00000000000000000000000000011011
Red (1), Green (8), Blue (2) 1+8+2=11 11 00000000000000000000000000001011

Notice the 32 bit representation of the value of the colors above.  Each bit that is ON is 1 and the ones that are OFF are 0.

So how would you figure out what enumeration item values where ON with out the computer?  All you need to know is the enumeration value and the values of the individual enumeration items.  Lets take the value 27 for example. Going from highest enumeration item value that can go into the value we can figure out what colors are ON in the enumeration value.

27 – 16 (White) = 11
11 – 8 (Green) = 3
3 – 2 (Blue) = 1
1 – 1 (Red) = 0
Answer = White, Green, Blue and Red.

The value of myColor variable would get saved to the database as an integer value. I wondered how or if I could use SQL to figure out which items where ON or OFF.  As long as I know the value of the enumeration items I am looking for I can use that information to query the data.  Look at the following SQL script to see how to do it.

declare @colors as table (
    id int,
    name nvarchar(100) not null,
    colors int not null
)

insert @colors (id, name, colors)
values
(1, 'Red', 1),
(2, 'Red, Green', 9),
(3, 'Red, Green, Blue', 11),
(4, 'Red, Green, Blue, White', 27),
(5, 'Red, Green, Blue', 11)

--lets get all the rows that have white in them
--we know white is 16
select *
from @colors
where colors & 16 = 16

--lets get all the rows that have blue and green but not white
--we know blue = 2m green = 8 and white = 16
select *
from @colors
where colors & 2 = 2 and colors & 8 = 8 and colors & 16 = 0

 

My Thoughts

While the initial issue with using this technique is that you are limited to the number of items in the enumeration depending on what data type you use.  I feel if you know you will use less then the maximum number of items in the enumeration this implementation would work.  Not having to add a new column to a database table every time you add an attribute sounds like a real win.

No comments:

Post a Comment