Enums in Dart are worth an extension
Note! Time has moved on and in the meantime Dart has capabilities that make this post obsolete. You can read about the new features here
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#.
Let’s 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 bit field.
[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 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 go-to solution for that problem. It just helped me to get some 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!