Ultimate AS3 Fake Enums
I just can’t leave this thing alone. This is hopefully the last revision I’m going to make to my fake AS3 enum class (here’s the original posting, then two updates). This is inspired by commenter Mikko Korpela, who asked about an iteration ability. Well, that sounds like a fun idea! Let’s fill in some holes and bring it up to par with the enum support in C#.
Sorry it took me so long to get around to this Miko, but I’ve had my Flex work on hold for a while. But recently I did some Flash work at PopCap and it got me back in the mood again.
New Enum Features
Here’s my Ultimate AS3 Fake Enum class, which has the following new features:
- Further type safety. Only the enum can construct its own constants now, via enum constructor protection. This should prevent people using the constants incorrectly (e.g. someone is incorrectly new’ing them up instead of using MyEnum.MyConstant).
- Every constant now gets a zero-based index that corresponds to the order it is created in the class. Access via the Index accessor on the constant. Useful in serialization code, for example.
- Added a new GetConstants method that returns an array of all the constants, where each constant’s slot in the array corresponds to its Index.
- Added a new ParseConstant method that does the reverse of toString(). Given a constant’s Name, return the constant. Case sensitivity optional and off by default for convenience.
…all without any extra burden placed on the derivative class! I’m always interested in solutions that keep client code as clean as possible.
I also adjusted the code to my current AS3 coding standards, which is nearly the same as Microsoft’s C# standards. Note that I’d normally use Vector.<Enum> instead of the generic Array class, but I haven’t upgraded my Flex to the newest SDK yet.
The Code
Without further ado, here’s the new Enum.as file. Stick it in a package if you wish, and drop it wherever you like. No system setup is required to make it work.
package
{
import flash.utils.*;
public /*abstract*/ class Enum
{
public function get Name() :String { return _name; }
public function get Index() :int { return _index; }
public /*override*/ function toString() :String { return Name; }
public static function GetConstants(i_type :Class) :Array
{
var constants :EnumConstants = _enumDb[getQualifiedClassName(i_type)];
if (constants == null)
return null;
// return a copy to prevent caller modifications
return constants.ByIndex.slice();
}
public static function ParseConstant(
i_type :Class,
i_constantName :String,
i_caseSensitive :Boolean = false) :Enum
{
var constants :EnumConstants = _enumDb[getQualifiedClassName(i_type)];
if (constants == null)
return null;
var constant :Enum = constants.ByName[i_constantName.toLowerCase()];
if (i_caseSensitive && (constant != null) && (i_constantName != constant.Name))
return null;
return constant;
}
/*-----------------------------------------------------------------*/
/*protected*/ function Enum()
{
var typeName :String = getQualifiedClassName(this);
// discourage people new'ing up constants on their own instead
// of using the class constants
if (_enumDb[typeName] != null)
{
throw new Error(
"Enum constants can only be constructed as static consts " +
"in their own enum class " + "(bad type='" + typeName + "')");
}
// if opening up a new type, alloc an array for its constants
var constants :Array = _pendingDb[typeName];
if (constants == null)
_pendingDb[typeName] = constants = [];
// record
_index = constants.length;
constants.push(this);
}
protected static function initEnum(i_type :Class) :void
{
var typeName :String = getQualifiedClassName(i_type);
// can't call initEnum twice on same type (likely copy-paste bug)
if (_enumDb[typeName] != null)
{
throw new Error(
"Can't initialize enum twice (type='" + typeName + "')");
}
// no constant is technically ok, but it's probably a copy-paste bug
var constants :Array = _pendingDb[typeName];
if (constants == null)
{
throw new Error(
"Can't have an enum without any constants (type='" +
typeName + "')");
}
// process constants
var type :XML = flash.utils.describeType(i_type);
for each (var constant :XML in type.constant)
{
// this will fail to coerce if the type isn't inherited from Enum
var enumConstant :Enum = i_type[constant.@name];
// if the types don't match then probably have a copy-paste error.
// this is really common so it's good to catch it here.
var enumConstantType :* = Object(enumConstant).constructor;
if (enumConstantType != i_type)
{
throw new Error(
"Constant type '" + enumConstantType + "' " +
"does not match its enum class '" + i_type + "'");
}
enumConstant._name = constant.@name;
}
// now seal it
_pendingDb[typeName] = null;
_enumDb[typeName] = new EnumConstants(constants);
}
private var _name :String = null;
private var _index :int = -1;
private static var _pendingDb :Object = {}; // typename -> [constants]
private static var _enumDb :Object = {}; // typename -> EnumConstants
}
}
// private support class
class EnumConstants
{
public function EnumConstants(i_byIndex :Array)
{
ByIndex = i_byIndex;
for (var i :int = 0; i < ByIndex.length; ++i)
{
var enumConstant :Enum = ByIndex[i];
ByName[enumConstant.Name.toLowerCase()] = enumConstant;
}
}
public var ByIndex :Array;
public var ByName :Object = {};
}
The Samples
Here are a couple sample enums and some code to demonstrate the features.
Simple Enum: State
This is the simplest usage of the Enum class. All you have to do to make a fake enum is:
- Create a class that extends the Enum class. Tag it as final too so nobody inherits it (that would be weird).
- Put some static init code in there that just calls initEnum(MyTypeName). This will get run on demand, on the first usage of the enum by other code. It sets up the necessary lookup tables and attaches a Name and Index to each enum constant.
- Create a public static const for each enum constant you want, following the pattern in the sample.
Here’s the sample enum. Note that it includes a bug on purpose! (It’s clearly marked.)
package
{
public final class State extends Enum
{
{initEnum(State);} // static ctor
public static const Initializing :State = new State();
public static const Connecting :State = new State();
public static const Loading :State = new State();
public static const Ready :State = new State();
// illegal; this will fail at runtime
public static const Test :OtherEnum = new OtherEnum();
}
}
And some test code to walk through the features…
for each (var state :State in Enum.GetConstants(State))
{
trace(state.Index + ": " + state.Name);
}
trace("connecting (no case): " + Enum.ParseConstant(State, "connecting"));
trace("connecting (with case): " + Enum.ParseConstant(State, "connecting", true));
// illegal; gets a runtime exception
var someEnum :State = new State();
Which produces the following output:
0: Initializing 1: Connecting 2: Loading 3: Ready connecting (no case): Connecting connecting (with case): null Error: Enum constants can only be constructed as static consts in their own enum class (bad type='State') ...
Advanced Enum: NetError
Let’s look at a more advanced sample, which I’m calling an “attributed” enum. Let’s say we have a NetError enum with constants for different types of failures on a network request. We want to be able to show a more user-friendly error, and perhaps classify it as a fatal error or not. We could have a table that associates extended attributes for enum constants with those constants, but why not just attach them to the constants directly?
In .NET I’d do this with attributes. Now, AS3 doesn’t support attributes like we need, so we’ll have to fake it. Given that we already have a fake enum constant, which is really an object underneath, we can attach whatever we like to it!
Take a look at the sample enum.
package
{
public final class NetError extends Enum
{
{ initEnum(NetError); } // static ctor
// Constants.
public static const None :NetError = new NetError(false, "no error");
public static const CantConnect :NetError = new NetError(false, "can't connect to the server, must be down?");
public static const VersionMismatch :NetError = new NetError(true, "protocol versions different");
public static const Unknown :NetError = new NetError(true, "unexpected error type");
// Constant attributes.
public function get IsFatal() :Boolean { return _isFatal; }
public function get Description() :String { return _description; }
// Constant query.
public static function GetConstants() :Array
{ return Enum.GetConstants(NetError); }
public static function ParseConstant(i_constantName :String, i_caseSensitive :Boolean = false) :NetError
{ return NetError(Enum.ParseConstant(NetError, i_constantName, i_caseSensitive)); }
// Private.
/*private*/ function NetError(i_isFatal :Boolean, i_description :String)
{ _isFatal = i_isFatal; _description = i_description; }
private var _isFatal :Boolean;
private var _description :String;
}
}
Given a constant, perhaps returned from a function as an error code, or thrown as part of an exception, we can ask it for its friendlier name by accessing its Description member.
Also in this class note the helper functions GetConstants and ParseConstant that wrap the Enum class versions, automatically passing in the local type. This isn’t necessary, but is a nice convenience. It is a bit more code to copy-paste around when adding new enums, though. If only AS3 supported templates, we could have a TEnum base that does all this for us. Ah well.
Here is some test code to play with the features
for each (var netError :NetError in NetError.GetConstants())
{
trace(
netError.Index + ": " + netError.Name +
" (" + netError.Description + ") " +
(netError.IsFatal ? "[FATAL]" : ""));
}
Which produces the following output:
0: None (no error) 1: CantConnect (can't connect to the server, must be down?) 2: VersionMismatch (protocol versions different) [FATAL] 3: Unknown (unexpected error type) [FATAL]
Future?
I hope this is the last word on fake enums in Actionscript. Not because I don’t think anyone else can improve on it, but because I’m hoping that the next version of the language will have native support for enums. Then we can put all this fakery behind us and get on with our lives.
The enum posts are among the top hitters on my blog, and they are popular topics elsewhere on the web. I’m hoping that Adobe will strongly consider native enum support in their next Flash player.


Great work!
As I my self have also been thinking of these enums heres some suggestions:
- add a K to my name ( I know that it’s my bad for typing my name incorrectly first time )
- I would prefer a helper class EnumConstants to be directly visible in subclasses for example by returning it from the initEnum – could be renamed to createEnumConstants or something
- allow one const EnumConstant field in every subclass
this way client code would have only one plays were class name should be mentioned:
public static const values :EnumConstants = createEnumConstants(ThisEnumClassName);
anyway great work! Thank you!
Mikko Korpela
25 Dec 09 at 3:47 am
Thanks for the feedback.
I think that minimizing class name usage within the enum class is a good idea, in order to reduce copy-paste error. Perhaps it would be better to have a
…and then reference that from functions like GetConstants as well as the original initEnum() call. Note that I haven’t tried this out, but if it compiles, it’s probably a good way to reduce copy-paste errors.
The problem I have with a helper class is that it’s still necessarily generic, and so you lose the type safety you get with NetError.ParseConstant. You could inherit from the helper class to provide type-specific behavior but then it’s back to having the classname in multiple places.
Scott
26 Dec 09 at 2:40 pm
[...] [...]
AS3 Lecks - Flashforum
6 May 10 at 2:28 am
If I place the Enum class in a package, I get a compile error on the line “var enumConstant :Enum = ByIndex[i];” in the EnumConstants class, saying:
Type was not found or was not a compile-time constant: Enum.
Any idea?
Joost
25 Jun 10 at 12:47 am
Couple possibilities I can think of – either your package needs importing, or you’re using an old Actionscript that does not support static typing.
Scott
27 Jun 10 at 7:58 pm
Thanks for your reply. I’ve added an import statement to the private support class and now it works.
package my.package.enum { public class Enum { ... } } class EnumConstants { import my.package.enum.Enum; /* this fixed my problem */ ... }BTW: I’m using Flash Builder 4.0 with the Flex SDK 4 (and AS3 but that should not be a surprise).
I wonder why I had to add this import statement.
Thanks for this Enum class and examples. I really like it!
Joost
1 Jul 10 at 5:47 am