New Fun Blog – Scott Bilas

Take what you want, and leave the rest (just like your salad bar).

Ultimate AS3 Fake Enums

with 16 comments

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.

[as3]
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 = {};
}
[/as3]

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:

  1. Create a class that extends the Enum class. Tag it as final too so nobody inherits it (that would be weird).
  2. 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.
  3. 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.)

[as3]
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();
}
}
[/as3]

And some test code to walk through the features…

[as3]
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();
[/as3]

Which produces the following output:

[text]
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’)

[/text]

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.

[as3]
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;
}
}
[/as3]

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

[as3]
for each (var netError :NetError in NetError.GetConstants())
{
trace(
netError.Index + ": " + netError.Name +
" (" + netError.Description + ") " +
(netError.IsFatal ? "[FATAL]" : ""));
}
[/as3]

Which produces the following output:

[text]
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]
[/text]

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.

December 24th, 2009 at 2:00 pm

Posted in as3,enum,flex

16 Responses to 'Ultimate AS3 Fake Enums'

Subscribe to comments with RSS or TrackBack to 'Ultimate AS3 Fake Enums'.

  1. 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

  2. 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

    [as3]
    private static const _enumType :Class = ThisEnumClassName;
    [/as3]

    …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

  3. […] […]

  4. 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

  5. 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

  6. Thanks for your reply. I’ve added an import statement to the private support class and now it works.

    [code]
    package my.package.enum
    {
    public class Enum
    {

    }
    }

    class EnumConstants
    {
    import my.package.enum.Enum; /* this fixed my problem */


    }
    [/code]

    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

  7. Thanks a lot for this superb code! I’ve been looking for a type-safe implementation for a while 😉 I love it!

    John Willemse

    14 Sep 10 at 7:54 am

  8. […] 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 […]

  9. Has the initialisation order in Flash changed? When I create a new enum based of the Enum class (say State), it calls the Enum constructor before calling the State static constructor, and hence fails.

    E.g.:
    [code]
    package
    {
    import flash.display.Sprite;

    public class TestOrder extends Sprite
    {

    public function TestOrder()
    {
    trace( "— creating State" );
    var s:State = new State;
    trace( "— finished creating State" );
    }

    }

    }

    // shortened enum class
    class Enum
    {
    protected static function init():void
    {
    trace( "Init function on enum" );
    }

    public function Enum()
    {
    trace( "Constructor for Enum" );
    }
    }

    // the enum that we’re creating
    class State extends Enum
    {
    {
    trace( "Static constructor for State" );
    init();
    }

    public static const BLAH:State = new State;
    }
    [/code]

    Here, my trace is:

    Constructor for Enum
    Static constructor for State
    Init function on enum
    — creating State
    Constructor for Enum
    — finished creating State

    Where the “Static constructor for State” should be before “Constructor for Enum” for your code to work or it fails on the error “Enum constants can only be constructed as static consts…”.

    Is this how it works your end as well, or am I missing something basic?

    I think I’m going to have to split out the Enum constructor code to an init function.

    In any case, thanks for the excellent class!

    Damian

    5 Jan 11 at 3:54 am

  10. Ha, ignore that last message, I’m being an idiot – the problem comes from the “var s:State = new State” code in the test file.

    Damian

    5 Jan 11 at 4:04 am

  11. Hey Scott — pretty nice thorough implementation. I like the fact that you included the ability to use the ParseConstant command for setting values, including the ability to allow a different case passed to get the enum value. My only beef there is that often times one would like have a value like this:

    public static const UPSIDE_DOWN:Orientation = new Orientation();

    However, if this is a client app getting data from the server, the server value might be the camelcase version of the value, i.e. “upsideDown”, instead of a lowercase underscore delimited version. I don’t know if that is a pretty rare case for folks, but I thought I’d note it and say that it could be worked around by changing the “toLowerCase() in the EnumConstants constructor and in the ParseConstants function to something like “toLowerCase().replace(/_/gi, “”)”.

    Thanks again!

    Ryan

    23 Jan 12 at 10:55 am

  12. Hey, I appreciate your work on your enum class! I’d like to utilize it in a project, was wondering about the nature of using it? Let me know, thanks.

    Edward

    25 Jul 14 at 6:54 am

  13. Use it however you like. Fully public domain.

    Scott

    26 Jul 14 at 11:29 pm

  14. Hi!

    This is my modified version: (with Vectors, Dictionaries, and extra function to get Enum object by index.)

    Thanks for your work!

  15. Thanks for this !
    I am currently reporting a java package to flex/action script (as a novice into these 2 languages), with a large usage of enums.
    You saved my days !

    Dude76

    26 Apr 17 at 1:04 am

Leave a Reply