Create bitwise Inheritance in a C# Enum

I’m not too sure if creating a bitwise inheritance construct in an enumeration is a horrible idea or a useful technique. Regardless, this post will show you how to create an enumeration (enum) type where each of its items are a bitwise value and, furthermore, each item may inherit the value of another enum item.

To demonstrate this awful idea (or cool technique), let’s suppose we would like to create one enum of Foods. The enum will consist of generic type of foods such as Fruit and Vegetables, but it will also consist of items that are more specific such as Apples, Bananas, and Broccoli.

Let’s continue to suppose that, in our application, we would like the user of the enum object to be able to set his enum variable to, say Broccoli, without having to ALSO specify his enum variable as a Vegetable. For example: We don’t want the user of the enum to have to do something like this:

// Foods is an enum
Foods food = Foods.Broccoli | Foods.Vegatable;

Instead, we would like to design the enum in such a way that the user can simply do this:

// Foods is an enum
Foods food = Foods.Broccoli;

And then, when the user needs to determine the value of the enum object, he can do something like this:

// Foods is an enum
Foods food = Foods.Broccoli;

// Some code here...

// Now we need to parse the food object - Notice how we can determine that the food object is a Vegetable even though we set the food object to .Broccoli!
if((food & Foods.Vegetable) == Foods.Vegetable)
 Console.WriteLine("I am a vegetable!");

In order to accomplish the technique above, we need to implement bitwise values for each of the enum values.

enum Foods : ulong
{
        // Level 1
	Fruit 		= 0x1,
	Vegetable 	= 0x2,
	Meat		= 0x4,
	Grain		= 0x8,
	Dairy		= 0x10,
	
	// Level 2 
	Apple		= Fruit | 0x100,
	Orange		= Fruit	| 0x200,
	Banana		= Fruit | 0x400,
	Pear		= Fruit | 0x800,
	
	Broccoli	= Vegetable | 0x100,
	Spinich		= Vegetable | 0x200,
	Celery		= Vegetable | 0x400,
	
	Steak		= Meat | 0x100,
	Chicken		= Meat | 0x200,
	Pork		= Meat | 0x400,
	
	Rice		= Grain | 0x100,
	Wheat		= Grain | 0x200,
	
	Cheese		= Dairy | 0x100,
	
	// Level 3
	Tritip		= Steak | 0x10000,
	Ribeye		= Steak | 0x20000,
}

Now in your application, you can instantiate an object of the Foods enum type and run bitwise operations on the object to determine what type of food the object is:

	Foods food = Foods.Broccoli;
	
	if((food & Foods.Vegetable) == Foods.Vegetable)
		Console.WriteLine("I am a vegetable!");
		
	food = Foods.Ribeye;
	
	if((food & Foods.Steak) == Foods.Steak)
		Console.WriteLine("I am a steak!");
		
	if((food & Foods.Ribeye) == Foods.Ribeye)
		Console.WriteLine("I am a ribeye!");

// Output:
I am a vegetable!
I am a steak!
I am a ribeye!

The critical construct that makes the bitwise enum work is the fact that each level (Level 1, Level 2, Level 3, and so on) reserve a set of bits that no other level can use.

In our example above, Level 1 reserves bits 1, 2, 3, 4, and 5. Specifically, the enum item Fruit is given the bit 1 slot (decimal value of 1 = 00001), Vegetable is given the bit 2 slot (decimal value of 2 = 00010), Meat is given the bit 3 slot (decimal value of 4 = 00100), Grain is given the bit 4 slot (decimal value of 8 = 01000), and Dairy is given the bit 5 slot (decimal value of 16 = 10000).

Level 2 has the following bit value schema:
The enum item Apple is given the bit 9 slot (decimal value of 256 = 100000000), Orange is given the bit 10 slot (decimal value of 512 = 1000000000), Banana is given the bit 11 slot (decimal value of 1024 = 10000000000), Pear is given the bit 12 slot (decimal value of 2048 = 100000000000), and so on. In addition to reserving those bit slots for each of the Level 2 items, we have OR’d the value using the bitwise symbol “|” so that the item “inherits” one of the Level 1 enum items. Or’ing the enum item with another level will set the item with its parent bitwise value as well as the value that you define. This will not only make the value of the enum item unique, but will also allow you to run bitwise operations so that you can interrogate the item to determine what type of food it is.

Here is a working console application that uses an five level bitwise-inheriting enum.

// There is no need for the Flags attribute because the Flags attribute allows you to combine two or more enum elements
// Which then, once the bitwise operatators have been applied to the enum variable, the variable is equal to whichever enum item has a matching value as the variable.
// In our case with this inheritance idea, combining more than one enum item (i.e. ParameterTypes v = ParameterTypes.Sensor | ParameterTypes.Conductivity) will never 
// equate to another enum item value.
enum ParameterTypes : ulong
{
        // Note that we use bit shifting to set our values of the enum items simply because it is easier to keep track of where we are at in the bitwise fields.
        // Also, note that we cast the integer value of "1" to a "ulong" data type. The reason for this is because the enum item values require more than 31 bits.
        // If we didn't cast the integer to a ulong, the bit twiddling would circle around where "1 << 32" would return "1" instead of "4294967296".
	// Level 1 reserves bits 0-7
	Parameter 				= (ulong)0,
	Disabled 				= (ulong)1 << 0,
	System 					= (ulong)1 << 1,
	IO 					= (ulong)1 << 2,
		
	// Level 2 reserves bits 8-15
	Input					= IO | (ulong)1 << 8,
	Output					= IO | (ulong)1 << 9,
	
	// Level 3 reserves bits 16-23
	VacantInput				= Input | (ulong)1 << 16,
	CalculatedInput			= Input | (ulong)1 << 17,
	ManualInput				= Input | (ulong)1 << 18,
	AnalogSensor			= Input | (ulong)1 << 19,
	DigitalSensor			= Input | (ulong)1 << 20,
	
	VacantOutput			= Output | (ulong)1 << 16,
	VariableFrequencyOuput 	= Output | (ulong)1 << 17,
	PowerRelay				= Output | (ulong)1 << 18,
	CurrentLoopOutput		= Output | (ulong)1 << 19,
	
	// Level 4 reserves bits 24-31
	CurrentLoopInput		= AnalogSensor | (ulong)1 << 24,
	Conductivity			= AnalogSensor | (ulong)1 << 25,
	pH						= AnalogSensor | (ulong)1 << 26,
	ORP						= AnalogSensor | (ulong)1 << 27,
	Corrosion				= AnalogSensor | (ulong)1 << 28,
	Temperature				= AnalogSensor | (ulong)1 << 29,
	
	ContactSet				= DigitalSensor | (ulong)1 << 24,
	Meter					= DigitalSensor | (ulong)1 << 25,
	
	VariableSpeedPump		= VariableFrequencyOuput | (ulong)1 << 24,
	
	Pump					= PowerRelay | (ulong)1 << 24,
	Valve					= PowerRelay | (ulong)1 << 25,
	
	// Level 5 reserves bits 32-39
	TowerConductivity 		= Conductivity | (ulong)1 << 32,
	BoilerConductivity 		= Conductivity | (ulong)1 << 33,
	CondensateConductivity 	= Conductivity | (ulong)1 << 34,
	
	FlowSwitch 				= ContactSet | (ulong)1 << 32,
	
	MakeupMeter				= Meter | (ulong)1 << 32,
	BleedMeter				= Meter | (ulong)1 << 33,
	
	ChemicalPump			= Pump | (ulong)1 << 32,
	
	SolenoidValve			= Valve | (ulong)1 << 32,
	MotorizedValve			= Valve | (ulong)1 << 33
}
void Main()
{
	ParameterTypes pt1 = ParameterTypes.SolenoidValve;
	
	if((pt1 & ParameterTypes.Parameter) == ParameterTypes.Parameter)
		Console.WriteLine("Associated the parameter with an Parameter type");
		
	if((pt1 & ParameterTypes.Disabled) == ParameterTypes.Disabled)
		Console.WriteLine("Associated the parameter with an Disabled type");
		
	if((pt1 & ParameterTypes.IO) == ParameterTypes.IO)
		Console.WriteLine("Associated the parameter with an IO type");
		
	if((pt1 & ParameterTypes.System) == ParameterTypes.System)
		Console.WriteLine("Associated the parameter with an System type");
		
	if((pt1 & ParameterTypes.Input) == ParameterTypes.Input)
		Console.WriteLine("Associated the parameter with an Input type");
		
	if((pt1 & ParameterTypes.Output) == ParameterTypes.Output)
		Console.WriteLine("Associated the parameter with an Output type");
		
	if((pt1 & ParameterTypes.VacantInput) == ParameterTypes.VacantInput)
		Console.WriteLine("Associated the parameter with an VacantInput type");
		
	if((pt1 & ParameterTypes.CalculatedInput) == ParameterTypes.CalculatedInput)
		Console.WriteLine("Associated the parameter with an CalculatedInput type");
		
	if((pt1 & ParameterTypes.ManualInput) == ParameterTypes.ManualInput)
		Console.WriteLine("Associated the parameter with an ManualInput type");
		
	if((pt1 & ParameterTypes.AnalogSensor) == ParameterTypes.AnalogSensor)
		Console.WriteLine("Associated the parameter with an AnalogSensor type");
		
	if((pt1 & ParameterTypes.DigitalSensor) == ParameterTypes.DigitalSensor)
		Console.WriteLine("Associated the parameter with an DigitalSensor type");
		
	if((pt1 & ParameterTypes.VariableFrequencyOuput) == ParameterTypes.VariableFrequencyOuput)
		Console.WriteLine("Associated the parameter with an VariableFrequencyOuput type");
	
	if((pt1 & ParameterTypes.PowerRelay) == ParameterTypes.PowerRelay)
		Console.WriteLine("Associated the parameter with an PowerRelay type");
		
	if((pt1 & ParameterTypes.VacantOutput) == ParameterTypes.VacantOutput)
		Console.WriteLine("Associated the parameter with an Disabled type");
		
	if((pt1 & ParameterTypes.CurrentLoopOutput) == ParameterTypes.CurrentLoopOutput)
		Console.WriteLine("Associated the parameter with an CurrentLoopOutput type");
		
	if((pt1 & ParameterTypes.CurrentLoopInput) == ParameterTypes.CurrentLoopInput)
		Console.WriteLine("Associated the parameter with an CurrentLoopInput type");
		
	if((pt1 & ParameterTypes.Conductivity) == ParameterTypes.Conductivity)
		Console.WriteLine("Associated the parameter with an Conductivity type");
		
	if((pt1 & ParameterTypes.pH) == ParameterTypes.pH)
		Console.WriteLine("Associated the parameter with an pH type");
		
	if((pt1 & ParameterTypes.ORP) == ParameterTypes.ORP)
		Console.WriteLine("Associated the parameter with an ORP type");
		
	if((pt1 & ParameterTypes.Corrosion) == ParameterTypes.Corrosion)
		Console.WriteLine("Associated the parameter with an Corrosion type");
		
	if((pt1 & ParameterTypes.Temperature) == ParameterTypes.Temperature)
		Console.WriteLine("Associated the parameter with an Temperature type");
		
	if((pt1 & ParameterTypes.ContactSet) == ParameterTypes.ContactSet)
		Console.WriteLine("Associated the parameter with an ContactSet type");
		
	if((pt1 & ParameterTypes.Meter) == ParameterTypes.Meter)
		Console.WriteLine("Associated the parameter with an Meter type");
		
	if((pt1 & ParameterTypes.Pump) == ParameterTypes.Pump)
		Console.WriteLine("Associated the parameter with an Pump type");
	if((pt1 & ParameterTypes.Valve) == ParameterTypes.Valve)
		Console.WriteLine("Associated the parameter with an Valve type");
		
	if((pt1 & ParameterTypes.TowerConductivity) == ParameterTypes.TowerConductivity)
		Console.WriteLine("Associated the parameter with an TowerConductivity type");
		
	if((pt1 & ParameterTypes.BoilerConductivity) == ParameterTypes.BoilerConductivity)
		Console.WriteLine("Associated the parameter with an BoilerConductivity type");
		
	if((pt1 & ParameterTypes.CondensateConductivity) == ParameterTypes.CondensateConductivity)
		Console.WriteLine("Associated the parameter with an CondensateConductivity type");
		
	if((pt1 & ParameterTypes.FlowSwitch) == ParameterTypes.FlowSwitch)
		Console.WriteLine("Associated the parameter with an FlowSwitch type");
		
	if((pt1 & ParameterTypes.MakeupMeter) == ParameterTypes.MakeupMeter)
		Console.WriteLine("Associated the parameter with an MakeupMeter type");
		
	if((pt1 & ParameterTypes.BleedMeter) == ParameterTypes.BleedMeter)
		Console.WriteLine("Associated the parameter with an BleedMeter type");
		
	if((pt1 & ParameterTypes.ChemicalPump) == ParameterTypes.ChemicalPump)
		Console.WriteLine("Associated the parameter with an ChemicalPump type");
		
	if((pt1 & ParameterTypes.MotorizedValve) == ParameterTypes.MotorizedValve)
		Console.WriteLine("Associated the parameter with an MotorizedValve type");
		
	if((pt1 & ParameterTypes.SolenoidValve) == ParameterTypes.SolenoidValve)
		Console.WriteLine("Associated the parameter with an SolenoidValve type");
		
	Console.WriteLine("\r\nHere are the values of each ParameterTypes items...\r\n");
	var enumVals = Enum.GetValues(typeof(ParameterTypes));
	foreach(ParameterTypes pt in enumVals)
	{
		long p = (long)pt;
		Console.WriteLine("{0},{1},{2}", pt, Convert.ToString(p, 2), (ulong)p);
	}
}


Output:
Associated the parameter with an Parameter type
Associated the parameter with an IO type
Associated the parameter with an Output type
Associated the parameter with an PowerRelay type
Associated the parameter with an Valve type
Associated the parameter with an SolenoidValve type

Here are the values of each ParameterTypes items...

Parameter,0,0
Disabled,1,1
System,10,2
IO,100,4
Input,100000100,260
Output,1000000100,516
VacantInput,10000000100000100,65796
VacantOutput,10000001000000100,66052
CalculatedInput,100000000100000100,131332
VariableFrequencyOuput,100000001000000100,131588
ManualInput,1000000000100000100,262404
PowerRelay,1000000001000000100,262660
AnalogSensor,10000000000100000100,524548
CurrentLoopOutput,10000000001000000100,524804
DigitalSensor,100000000000100000100,1048836
VariableSpeedPump,1000000100000001000000100,16908804
Pump,1000001000000001000000100,17039876
CurrentLoopInput,1000010000000000100000100,17301764
ContactSet,1000100000000000100000100,17826052
Valve,10000001000000001000000100,33817092
Conductivity,10000010000000000100000100,34078980
Meter,10000100000000000100000100,34603268
pH,100000010000000000100000100,67633412
ORP,1000000010000000000100000100,134742276
Corrosion,10000000010000000000100000100,268960004
Temperature,100000000010000000000100000100,537395460
ChemicalPump,100000001000001000000001000000100,4312007172
FlowSwitch,100000001000100000000000100000100,4312793348
SolenoidValve,100000010000001000000001000000100,4328784388
TowerConductivity,100000010000010000000000100000100,4329046276
MakeupMeter,100000010000100000000000100000100,4329570564
MotorizedValve,1000000010000001000000001000000100,8623751684
BoilerConductivity,1000000010000010000000000100000100,8624013572
BleedMeter,1000000010000100000000000100000100,8624537860
CondensateConductivity,10000000010000010000000000100000100,17213948164

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s