Enums in Dart are worth an extension

Software developmentProjects
  |  

Enums in Dart are worth an extension

Dart is a relatively simple programming language.
In general this is a good thing as the number of possibilities and patterns you have to know in order to develop something with it or when reading code is lower.
On the other hand you will miss certain features that other programming languages have that can lead to quite elegant solutions for certain problems.

Enums int Dart are such a case.

Enums are very simple in Dart. In general they are classes / objects like everything else but you can’t do neat tricks with them like for example with C#.

Lets assume we have a byte value and want to read some bits of it. Those bits should have names (mapped to an enum) for better understandability.

Enums in C#

In C# there is a language feature for exactly that. You can use any enum to represent bits in a bitfield.

[Flags]
public enum FlagValues : byte
{
    None    = 0x00,
    Flag1 = 0x01 << 0,
    Flag2 = 0x01 << 1,
    Flag3 = 0x01 << 2,
    Flag4 = 0x01 << 3,
    Option4 = 0x01 << 4,
    All = 0xFF
};

// later in the code

byte val = 0x05; //00000101

var flags = (FlagValues) val;
flags.HasFlag(FlagValues.Flag1); // true
flags.HasFlag(FlagValues.Flag2); // false
flags.HasFlag(FlagValues.Flag3); // true
flags.HasFlag(FlagValues.Flag4); // false

You can even do bitwise operations directly with the enum like

FlagValues flags = FlagValues.Flag2;
flags |= FlagValues.Flag4;

to get the underlying byte you can just do a cast

byte val = (byte) flags;

So the enum basically “is” its value

Pimping enums in Dart

Dart doesn’t have such a feature packed enum. Enums are basically just a type with a specified list of states it can have. There is also no way to explicitly define the value for each enum member.

But Dart has the concept of extensions so we can get a little closer to what other languages offer by using them.

We extend the enum to provide a value mapping for each enum member, add a property value to it that can return that mapped value and some convenience methods that help with bit operations.

enum FlagValues {
    None,
    Flag1,
    Flag2,
    Flag3,
    Flag4,
    All
}

// all additional functionality has to go into the extension:

extension FlagValuesExtension on FlagValues {
  //a map containing all the values for the enum fields
  static final Map<FlagValues, int> _valueMap = {
    FlagValues.None: 0,
    FlagValues.Flag1: 0x01 << 0,
    FlagValues.Flag2: 0x01 << 1,
    FlagValues.Flag3: 0x01 << 2,
    FlagValues.Flag4: 0x01 << 3,
    FlagValues.All: 0xFF
  };
  //internal reverse map to resolve the enum from the value. Will be populated on demand
  static Map<int, FlagValues>? __reverseValueMap;
  //gets the reverse map and populates it when it is not yet built
  static Map<int, FlagValues> get _reverseValueMap {
    __reverseValueMap ??= {
      for (var entry in _valueMap.entries) entry.value: entry.key
    };
    return __reverseValueMap!;
  }

  //gets the FlagValues instance for the given value or [null] when there is none
  static FlagValues? fromValue(int value) =>
      FlagValuesExtension._reverseValueMap[value];

  //returns the value for this FlagValues
  int get value => _valueMap[this]!;

  //checks if this FlagValues is set in the given bit field
  bool isSetIn(int bitfield) => (bitfield & value) != 0;

  //sets the bit(s) that match(es) [this] in the given bitfield and returns the result
  int setIn(int bitField) => bitField | value;

  //erases the bit(s) that match(es) [this] in the given bitfield and returns the result
  int eraseIn(int bitfield) => bitfield & ~value;
}

We can’t mimic all of the behavior due to the way enums work in Dart. It is not possible to have an enum with a value that is not specified (like having two bits set) so we can’t implement the functionality the operators in C# deliver.

What is inside that extension is up to your needs. If you only need explicit values tied to an enum then you can just use that part of the example here and leave out the bit operation stuff.

I also don’t claim that this is the one and only best in class goto solution for that problem. It just helped me to get some of the features back that I’m used to and that are adding some extra elegance to the usage side.

final flag = FlagValues.Flag2;
// getting the value tied to the enum
final val = flag.value;
assert(val == 2);

//setting some bits
var bitfield = 0;
bitfield = FlagValues.Flag1.setIn(bitfield);
bitfield = FlagValues.Flag3.setIn(bitfield);
assert(bitfield == 5);
bitfield = FlagValues.Flag3.eraseIn(bitfield);
assert(bitfield == 1);

//check if bits are set
assert(!FlagValues.Flag2.isSetIn(bitfield));
assert(FlagValues.Flag1.isSetIn(bitfield));

Happy coding!