Below we have an EntityType enum which can identify both Natural- and Legal entities.
using System;
using System.ComponentModel;
namespace Bitwize.Enums
{
[Flags]
public enum EntityType
{
[Description("None")]
None = 0,
[DescriptionAttribute("Legal Entity")]
LegalEntity = (1 << 4),
[DescriptionAttribute("Natural")]
Natural = (1 << 5),
// Legal Entities
[DescriptionAttribute("Close Corporation")]
CloseCorp = LegalEntity | 1,
[DescriptionAttribute("Non Government Organisation")]
NGO = LegalEntity | 2,
[DescriptionAttribute("Body Corporate")]
BodyCorp = LegalEntity | 3,
// Natural Party Types
[DescriptionAttribute("Individual")]
Individual = Natural | 1,
[DescriptionAttribute("Joint Account")]
JointAccount = Natural | 2,
[DescriptionAttribute("Deceased Estate")]
DeceasedEstate = Natural | 3
}
}
Now let’s see how we got here.
Creating the EntityType enum
The Flags attribute
[Flags]
The Flags attribute marks the enum so that it can be treated as a bit field. To get our intended behaviour (distinguish between Natural- and Legal entities) for flags, the integer values need to be factors of 2. This ensures that there is no overlap between any combinations of flags, for example: There is no way for 0, 1 and 2 to combine and make 4. Read more about the Flags attribute on MSDN.
Shift left (“«”)
[DescriptionAttribute("Natural")]
Natural = (1 << 5),
This operation “shifts” the one’s in a binary representation to the left, by adding 0s to the end. Therefore the integer value 1 (00000001) shifted 5 bits to the left, becomes 32 (00100000). Read more about the Shift left operation on MSDN.
Bitwise OR (“|”)
[DescriptionAttribute("Individual")]
Individual = Natural | 1,
The return type of this operation is determined by its arguments. The operator calculates the binary representation of each argument and then does a bitwise OR operation on them. ‘(Natural | 1)’ would compute as follows: |
00100000 | 'Natural', integer value 32.
00000001 | 1
-------- | (Bitwise OR)
00100001 | The integer value 33.
Don’t make the mistake of assuming that this operator acts as an integer + operation (although 32 | 1 = 33 and 32 + 1 = 33), using 32 | 33 as an example: |
00100000 | Integer value 32.
00100001 | Integer value 33.
-------- | (Bitwise OR)
00100001 | The integer value 33.
Read more about the Bitwise OR operation on MSDN.
Putting it to use
Based on what we’ve learnt above, we know that the enum class could have just used explicit integer values. See this Gist. We also know that 2n & ( 2n + x ) = 2n.
Looking at our enum we can see that there are two “Macro” types of entities (Natural and Legal), which are then further divided into more specific entity types (Individuals and Body Corporates for example). If we have an Entity class with a property to identify its “Entity Type” and we store the integer value in the database (33 for Individual and 67 for Body Corporate) how would we be able to identify Natural entities which are Legal entities?
We could just check if an integer is within the range between 32 and 64, like this. Or we could apply what we’ve learnt from the Bitwise AND operator, if ‘Natural’ is 25 (32) and ‘Individual’ is 25+1 (33) we know that ‘Individual’ & ‘Natural’ == ‘Natural’, so we can add this property to our Entity class:
public class Entity
{
private EntityType _entityType { get; set; }
public Entity(EntityType entityType)
{
_entityType = entityType;
}
public bool IsLegalEntity
{
get
{
return (this._entityType & EntityType.LegalEntity) == EntityType.LegalEntity;
}
}
public bool IsNaturalEntity
{
get
{
return (this._entityType & EntityType.Natural) == EntityType.Natural;
}
}
}
Our tests will pass regardless of our implementation.
[Test]
public void Test_EntityType_Checks()
{
//arrange..
var individual = new Entity(EntityType.Individual);
var bodyCorp = new Entity(EntityType.BodyCorp);
//assert..
Assert.IsFalse(individual.IsLegalEntity);
Assert.IsTrue(individual.IsNaturalEntity);
Assert.IsTrue(bodyCorp.IsLegalEntity);
Assert.IsFalse(bodyCorp.IsNaturalEntity);
}
Conclusion
Pros
Robust and Maintainable: Adding another “Macro” type (like “Artificial”) and some sub types (“Toaster” and “Roomba”) is trivial. You just add another “Marco’ enum entry, shifted one more bit to the left than the previous one, then add another list of specific entries. Checking if an entity is ‘Artificial’ is just as simple:
public class Entity
{
...
public bool IsArtificial
{
get
{
return (this._entityType & EntityType.Artificial) == EntityType.Artificial;
}
}
}
...
[Flags]
public enum EntityType
{
...
[DescriptionAttribute("Artificial")]
Artificial = (1 << 6),
...
// Artificial Entities
[DescriptionAttribute("Toaster")]
Toaster = Artificial | 1,
[DescriptionAttribute("Roomba")]
Roomba = Artificial | 2,
}
What would happen if you run out of legal entity integers?* It’s not entirely inconceivable that there can be more than 16 types of Legal Entities (especially once the robots become self aware and are no longer happy being referred to as ‘Artificial’ entities). The only thing you would have to do is crank up the bit shifting for all the ‘Macro’ types one more bit to the left. You wouldn’t have to worry about any number range checking, or have to now update each entry in the enum’s integer value. Had we not been so diligent and placed the checks in the Entity class, we could have had to replace loads of hard-coded integers everywhere in the code. Using the flags, all of these checks would just keep working as expected. (Remember kids, Magic Numbers are evil.)
Cons
Complex: The biggest con for using flags to solve this problem is complexity. It might not be immediately clear to someone inheriting the code what exactly it does and how it works. Additionally, you might not see that you’re “running out of legal entity integers”. “Natural | 1” will evaluate to the same value as “Natural | 33” when “Natural” is 32 (or 1 « 5). Good code comments and appropriately defined unit tests could solve, or at least mitigate, these issues. |
Not an easy reference material: This somewhat ties in with the complexity con. If you’re looking at the database tables and see EntityType is equal to 34, it’s going to take you a while to determine that that refers to ‘JointAccount’(Natural | 2). |
*Hopefully you’d pick this up early enough or else you might also need to write some scripts to keep your database values consistent. This would be an issue with either approach.