Java-style Typesafe Enumerations in AS3
Enumerations are one of the major language features that I miss in AS3, especially when writing client-side versions of Java code which use Java’s robust enumerations. This has been discussed elsewhere quite a bit so I’ll just do a quick recap and then give my new updated version.
The simplest version of an enumeration you can do in AS3, of course (and the one most people use) are static const String values that are set in a class which is meant not to be instantiated.
package com.example { public class SimpleEnum { public static const ONE : String = "One"; public static const TWO : String = "Two"; public static const THREE : String = "Three"; } }
This works for the simplest cases but doesn’t come near to having the features of Java’s enumerations. One alternate version uses int values to make the conversion from Java to AS3 easier (Java uses int values to identify each enumeration value which is called an ordinal).
package com.example { public class SimpleEnum { public static const ONE : uint = 1; public static const TWO : uint = 2; public static const THREE : uint = 3; } }
The main problem with both of the above is that any time you use one of your SimpleEnum values, you are using a simple type (String or uint) which allows any random value to be passed in, not just those that you have defined.
The next step is to use instances of the Enum class for each one of the constants. This makes your enumerations typesafe and allows you to have your enum values be real objects with member functions and variables.
package com.example { public class SimpleEnum { public static const ONE : SimpleEnum = new SimpleEnum("One"); public static const TWO : SimpleEnum = new SimpleEnum("One"); public static const THREE : SimpleEnum = new SimpleEnum("One"); private var _name : String; public function get name() : String { return _name; } public SimpleEnum(inName : String) { _name = inName; } } }
This is great as it enforces that any time you want to use a value from SimpleEnum you can ensure that it’s the right type, but this has a different form of the same issue above. Other code can happily create new versions of SimpleEnum and use them wherever.
It is at this point that some of the more esoteric bits of AS3 coding can be helpful. Private constructors would fix this problem right away but AS3 also lacks this language feature. For Singletons, most developers use a private class which is appended to the same file, after the package block ends. Unfortunately, this doesn’t work as the “private” class is not yet loaded when the static const variables are being created. Luckily, static initializers fix this problem.
package com.example { public class SimpleEnum { public static const ONE : SimpleEnum = new SimpleEnum("One"); public static const TWO : SimpleEnum = new SimpleEnum("One"); public static const THREE : SimpleEnum = new SimpleEnum("One"); private var _name : String; public function get name() : String { return _name; } private static var _created : Boolean = false; //This code is run statically after the const vars above are created, closing off the constructor { _created = true; } public SimpleEnum(inName : String) { if (_created) { throw new Error("SimpleEnum is an enumeration, use the class constants instead."); } _name = inName; } } }
I can see only two downsides to this pattern. Any function parameter or variable which is of the SimpleEnum type could be set to null. This is a common issue with Object Oriented programming, though, and can happen with Java’s enum type as well, so this is minimal. The larger possible issue is that the error for trying to use new SimpleEnum() happens at run-time and not compile-time. This could bite users who mistakenly try to instantiate the SimpleEnum class in their code and could potentially not be found right away, since a specific set of run-time conditions may have to be satisfied to call the offending code. However, with a helpful enough error message and documentation I think that this is a small issue.
This is essentially the pattern that I have adopted but I’ve added a few more features. Specifically, I’ve implemented an ordinal value which can be used for interaction with Java enum values or for advanced serialization or hashing and have added a function to get all of the possible values for use in iteration (or enumeration of the values….;-) ). The creation checking is also done in the base class so that a minimum of extra work needs to be done in the concrete enum classes.
package com.reversefold.util { import flash.errors.IllegalOperationError; import flash.utils.Dictionary; import flash.utils.describeType; import flash.utils.getQualifiedClassName; /** * An abstract class to emulate Enum type. * * Subclasses should follow this pattern: * * package com.example { * public class TestEnum extends Enum { * public static const ONE : TestEnum = new TestEnum(); * public static const TWO : TestEnum = new TestEnum(); * public static const THREE : TestEnum = new TestEnum(); * * public static function get values() : Vector.<testenum> { * return Vector.</testenum><testenum>(Enum.getValues(TestEnum)); * } * * { * initEnumConstant(TestEnum); * } * } * } */ public class Enum { protected static var sequence : Dictionary = new Dictionary(); /** * To protect from instantiation after static initializing. */ protected static var values : Dictionary = new Dictionary();//.<vector .<Enum>> /** * Function to call for each enum type declared and in static init. */ protected static function initEnumConstant(inType : Class) : void { var className : String = getQualifiedClassName(inType); var typeXML : XML = describeType(inType); var newValues : Vector.<enum> = new Vector.</enum><enum>(); for each (var constantXML : XML in typeXML.constant) { var name : String = constantXML.@name; var constant : Enum = inType[name]; constant._label = name; newValues.push(constant); } //sort by ordinal (instantiation order) values[className] = newValues.sort( function(lhs : Enum, rhs : Enum) : Number { return lhs._ordinal > rhs._ordinal ? 1 : (lhs._ordinal < rhs._ordinal ? -1 : 0); } ); } protected static function getValues(inType : Class) : Vector.<Enum> { return Vector.</enum><enum>(values[getQualifiedClassName(inType)]); } /** * Enum label. */ private var _label : String; public function get label() : String { return _label; } private var _ordinal : uint; public function get ordinal() : uint { return _ordinal; } public function Enum() { var className : String = getQualifiedClassName(this); if (values[className]) { throw new IllegalOperationError("Cannot instantiate anymore: " + className); } var seq : uint; if (sequence[className] == null) { seq = 0; } else { seq = sequence[className] + 1; } _ordinal = seq; sequence[className] = seq; } public function toString() : String { return ordinal + " " + label; } } } </enum></vector></testenum>
And here is an example use:
package com.reversefold.shop.model { public class TestEnum extends Enum { public static const ONE : TestEnum = new TestEnum("One"); public static const TWO : TestEnum = new TestEnum("Two"); public static const THREE : TestEnum = new TestEnum("Three"); //This function gives typesafe access to the list of constants that Enum.getValue() gives public static function get values() : Vector.<testenum> { return Vector.</testenum><testenum>(Enum.getValues(TestEnum)); } //static initializer, make sure to pass in the current class as a parameter { initEnumConstant(TestEnum); } //read-only getter of the name value private var _name : String; public function get name() : String { return _name; } public function TestEnum(inName : String) : void { super(); _name = inName; } override public function toString() : String { return super.toString() + " " + _name; } } } </testenum>
Thanks to all of the linked blog postings for their ideas. I only found Scott Bilas’ post after I’d finished writing my own extension of Peter Moelgaard’s version. Scott’s does nearly the same things mine does, but in a more C#-like way.






















