« ChartDirector 7.0 Rel… | Home | News from the MBS Xoj… »

How binary enumeration is implemented in Xojo

You downloaded Xojo 2021r2 and looked into what is new and you may have found the binary enum. If you wonder what this is, let me give you a short explanation. Normal enums give names to integer values, so you can pass a name instead of knowing the value. Binary enums now are defined to give every bit in an integer a name, so you can toggle bits easily and store flags in the enum. For more details, check the Xojo blog.

Let's define a binary enum like this:

Module Module1
Enum Fruits Apple Orange Grape End Enum
End Module

Mark the checkbox for binary. Implicitly the first one gets a value of 1 for Apple, Orange is 2 and Grape 4. You can use custom values like AppleWithOrange = 3. We can use the enum now and benefit from OR operator and the built-in Contains functions:

Private Sub test() Dim t As Fruits t = Fruits.Apple Or Fruits.Grape Dim n As Integer = t If t.Contains(Fruits.Orange) Then Break End If If t.Contains(Fruits.Grape) Then Break End If Break End Sub

If you run this, you will see it stops at second break inside the test for Fruits.Grape. The variable n has the value 5.

Since the Xojo IDE generates a class for you automatically, you may inspect it with introspection to look what is offered. And of course you can use those enums like objects, e.g. put them in a variant, array or dictionary. The class generated for the above enumeration should look like this:

Class Fruits
Computed Property Value As Integer
Sub Get() return Self.InternalValue End Get
End Computed Property
Shared Function Apple() As Fruits Return New Fruits(1) End Function
Private Sub Constructor(value as integer) InternalValue = value End Sub
Function Contains(value as Fruits) As Boolean Return (Self.InternalValue And value.InternalValue) = value.InternalValue End Function
Shared Function Grape() As Fruits Return New Fruits(4) End Function
Function Operator_And(otherValue as Fruits) As Fruits Return New Fruits(Self.InternalValue And otherValue.InternalValue) End Function
Function Operator_AndRight(otherValue as Fruits) As Fruits Return New Fruits(otherValue.InternalValue And Self.InternalValue) End Function
Function Operator_Compare(otherValue as Fruits) As Integer Return Sign(Self.InternalValue - otherValue.InternalValue) End Function
Function Operator_Convert() As Integer Return Self.InternalValue End Function
Sub Operator_Convert(value as integer) InternalValue = value End Sub
Function Operator_Or(otherValue as Fruits) As Fruits Return New Fruits(Self.InternalValue Or otherValue.InternalValue) End Function
Function Operator_OrRight(otherValue as Fruits) As Fruits Return New Fruits(otherValue.InternalValue Or Self.InternalValue) End Function
Function Operator_Xor(otherValue as Fruits) As Fruits Return New Fruits(Self.InternalValue Xor otherValue.InternalValue) End Function
Function Operator_XorRight(otherValue as Fruits) As Fruits Return New Fruits(otherValue.InternalValue xor Self.InternalValue) End Function
Shared Function Orange() As Fruits Return New Fruits(2) End Function
Property Private InternalValue As Integer
End Class

As you see, this is a class with some shared factory methods to create new instances for the given names. Then the internal value is stored in a private integer property and can only be read using the value property. Various operators are defined to do math on the value properties directly. That includes both left and right variants, so the compiler always has one to call. You can do OR, AND and XOR operators, which call the methods. And with Operator_Convert the compiler can query the integer value or create a new enum with an integer value. As you see the Contains function is just another way to do AND operator.

Could this be done better? Yes, of course I wold have wished this would have been implemented as part of the compiler using an integer and not a class. But since we got the class, why does it need an InternalValue private property? Please just use value and let us assign it (Feedback case 65356 - Binary Enumeration: make value regular property). Make the constructor also public, so we can define our own methods doing "new Fruits(123)" passing any value (Feedback case 65359 - Binary Enumeration: Make constructor public). Of course you may just do the Operator_Convert instead, but I would prefer a simpler class without the Value computed property. And by the way, although this is a class, you can't subclass it. The class picker won't find the enum as class name. But you can extend the enum with methods in a module.

If you think about doing a + on the enum, e.g. Fruits.Apple + Fruits.Grape, this will fail. The Operator_Add method is missing.

t = Fruits.Apple.value + Fruits.Grape.value

With .value on the end, it will compile. The compiler will query the values as integer, do the add and then use Operator_Convert to make a new enum object. Feedback case 65357 - Binary Enumeration: Add Operator_Add. For some reason the value property doesn't even show in auto complete: Feedback case 65358 - Binary Enumeration: Value property does not auto complete.

Binary Enums can only be defined in a Module, since the IDE can put the class there. If you copy & paste or move one outside a module, you will run into weird compilation errors. My suggestion for a fix would be to have the IDE put all those binary enum outside a module into a new module just for them. Feedback case 65327 - Moving binary enumeration out of a module causes a syntax error when project is run.

Having this feature is great for many uses. It will make flag handling easier, help with assembling values for declares (pass as integer!) and using enums often helps to avoid coding errors. But this version 1.0 of the feature and I hope we soon get a few enhancements.

Plugins can't easily declare binary enums. A plugin developer would have to implement this as a class with same interface as above.

26 07 21 - 09:24