DEV Community

Ian Parr
Ian Parr

Posted on

Taking advantage of BitMasks

Binary
Recently I needed a way to store a finite list or error messages that could potentially occur during an async process.

I could have implemented something like - creating a relational table structure, store them as a JSON file etc. All of the alternatives felt a bit like 'overkill' for reasons I won't go into here. So I decided to opt for a Bitmask. Why? Well, essentially, bitmasks are a great way to store a large amount of 'information' in a small space.

Here are the potential errors that I needed to log.

  • invalidName
  • invalidDate
  • invalidTime
  • invalidType
  • invalidRange
  • invalidState

So what Bitmasking allowed me to do was to assign an integer value to each of the potential errors. So:

  • invalidName = 1
  • invalidDate = 2
  • invalidTime = 4
  • invalidType = 8
  • invalidRange = 16
  • invalidState = 32

Usage
In C# land we would implement something like this:

using System;

public class Program
{
    static int _errorTotal = 0;

    public static void Main()
    {           
        //simulate some errors
        _errorTotal += (int)Errors.Name;
        _errorTotal += (int)Errors.Date;
        _errorTotal += (int)Errors.Time;
        _errorTotal += (int)Errors.Type;

        Console.WriteLine("Error total: {0}", _errorTotal); // this is what we persist
        Console.WriteLine("Error list: {0}", (Errors)_errorTotal); // this is how we read the data back out
    }

    // Holds the value of each of the error types
    [Flags]
        public enum Errors 
    {
        Name = 1,
        Date = 2,
        Time = 4,
        Type = 8,
        Range = 16,
        State = 32
    }
}
Enter fullscreen mode Exit fullscreen mode

Output:
Error total: 15
Error list: Name, Date, Time, Type

We can now see how storing just one integer value, you can gain access to any combination of errors that occurred during the async process.

Note: Like I said at the start, this is a great way of storing a finite list of 'stuff' - for particularly long lists of 'stuff', you might want to cast your [Flag] enum to (long):

[Flags]
public enum Errors : long
{
    Name = (long)1,
    Date = (long)2,
    Time = (long)4,
        ...
Enter fullscreen mode Exit fullscreen mode

Top comments (3)

Collapse
 
rubberduck profile image
Christopher McClellan • Edited

Something that makes things a little more clear and easy to work with is if you define the enum in terms of powers of 2.

[Flags]
public enum errors
{
    Name = 2^0,
    Date = 2^1,
    Time = 2^2,
    // etc.
}
Enter fullscreen mode Exit fullscreen mode

Sadly, C# lacks a power operator.

Collapse
 
kspeakman profile image
Kasey Speakman • Edited

Alternative syntax to set flags.

_errors = _errors | Errors.Name;
_errors = _errors | Errors.Date;
...

// check present of a flag
var hasDateError = _errors.HasFlag(Errors.Date);

Alternative syntax to define them using C# 7 binary literals

[Flags]
public enum Errors : int
{
    // to represent absence of any flag
    None = 0b0000_0000_0000_0000,
    Name = 0b0000_0000_0000_0001,
    Date = 0b0000_0000_0000_0010,
    Time = 0b0000_0000_0000_0100,
    ...
}

Or shift syntax

[Flags]
public enum Errors : int
{
    // to represent absence of any flag
    None = 0,
    Name = 1 << 0, // 1
    Date = 1 << 1, // 2
    Time = 1 << 2, // 4
    ...
}