I was trying to create a typesafe enum today to hold a Debit and Credit value and realized it could not that easily be done in AS3. In AS2 or in Java (prior to Java 5 when there was no enum construct) you could do the trick by making the constructor of the enum private and instantiate the values as static constants. (See this blog post for an AS2 example.)
Unfortunately there is no such thing as a private constructor in AS3, so we need to find another way of restricting values. First thing I thought of was to use the internal class "hack" used to create singletons to ensure that instantiation can only happen internally in the enum class. However that does not work (probably since the internal class is not yet loaded when trying to instantiate the values). Here's an example.
-
package com.domain {
-
class DebitCreditEnum {
-
public static const DEBIT:DebitCreditEnum = new DebitCreditEnum("debit", new EnumRestrictor());
-
public static const CREDIT:DebitCreditEnum = new DebitCreditEnum("credit", new EnumRestrictor());
-
-
private var _name:String;
-
-
public function DebitCreditEnum(name:String, restrictor:EnumRestrictor) {
-
Assert.notNull(restrictor, "The 'restrictor' argument must not be null"); //*
-
_name = name;
-
}
-
}
-
}
-
internal class EnumRestrictor {}
(* the Assert class is part of the Prana Framework)
The example above throws the following error:
TypeError: Error #1115: private::EnumRestrictor is not a constructor.
at com.domain::DebitCreditEnum$cinit()
at [newclass]
at global$init()
Apparently, it is impossible to call the constructor of the inner class in a static member of the top class. This also means that we can't create eagerly loaded singletons with the inner class approach. I can imagine that this makes sense in a way, since the inner class will probably be loaded after the top class has loaded and its static members have been initialized. However, I'm not sure about this, so if anyone knows feel free to comment on this.
Static code block to the rescue
AS3 introduces the concept of a static code block. The code inside this block will be executed when the class is loaded in memory. This happens before a constructor call and after all static members have been set. Using a static code block, we can restrict access to the constructor by setting a flag after we have defined the values for our enum. Here's the example:
-
package com.domain {
-
class DebitCreditEnum {
-
public static const DEBIT:DebitCreditEnum = new DebitCreditEnum("debit");
-
public static const CREDIT:DebitCreditEnum = new DebitCreditEnum("credit");
-
-
private static var _enumCreated:Boolean = false;
-
-
// magic happens here, the static code block
-
{
-
_enumCreated = true;
-
}
-
-
private var _name:String;
-
-
public function DebitCreditEnum(name:String) {
-
if (_enumCreated)
-
throw new Error("The enum is already created.");
-
_name = name;
-
}
-
}
-
}
Add to Bloglines - Digg This! - del.icio.us - Stumble It! - Twit This! - Technorati links - Share on Facebook - Feedburner
Christophe Herreman is a software developer living in Belgium. He's working on high-end Flex and AIR solutions at 
January 11th, 2008 at 4:39 pm
Thanks for the tip!
I too was trying the initial approach, which led me to your post.
August 14th, 2008 at 9:40 am
I had a similar problem but I found a work-around. It’s not the nicest one, but you could instantiate your DEBIT and CREDIT in the constructor of the DebitCreditEnum class. You put them as vars, you only provide getters (to make them constant to the outside), you set them as null in the beginning, and in the constructor you test if they are null in wich case you instantiate them.
September 15th, 2008 at 12:24 am
An interesting technique, but it does rely on the compiler not one day being optimised in a way that the static code blocks happen before static initialisations. The other question is of course since you’re going to be using typesafe checks against the public consts (and DebitCreditEnum is not final), what benefit do you really get by this code? Who cares if somebody calls new DebitCreditEnum() a few times? It can’t break anything relying on this enum class as far as I can see.
November 7th, 2008 at 8:30 pm
I recently came across the problem of neededing some enumerations in code I was writing. I tried to come up with an enumerated type as close to the ones used in c# as I could. I saw that an enum type was simply a collection of string values each basically creates as a static member of the enum type. I came up with a base enum class that then can be exteneded into any enum you wish.
package Enums.baseEnum
{
/** the base class for an enumerated type. The values for the type are determined by a string array that is passed in. A user can get/set the current
* value and the current index. Passing an invalid index or and invalid value to the setters will result in an argument error. To create a more standard
* enum type, simply extend this class, creating as many public static constants as you wish, then make an array of all the same constants, and pass to
* the super constructor of this class. For ease of use, remembering intellisence will alphabetize your enum lists, so keep your strings alphabetized
* or include the index as part of the static members name**/
public class Enum
{
public function Enum(values:Array, initialValue:String)
{
if (values == null)
_values = new Array();
else
_values = values;
this.value = defaultValue;
}
protected var _values:Array;
private var _index:int;
public function get index():int
{
return _index;
}
public function set index(val:int):void
{
if(val = _values.length)
throw new ArgumentError(“Enum set index : supplied value of ” + val + ” is outside current range of 0 – ” + _values.length – 1 + “, determined by this Enum’s array [" + _values.toString()"]“);
_index = value;
}
public function set value(value:String):void
{
_index = _values.indexOf(value);
if(_index == -1)
throw new ArgumentError(“Enum set value : supplied value of ” + value + ” is not a recognized entry, available values are ” + _values.toString());
}
public function get value():String
{
return _values[_index].toString();
}
}
}
this class requires an array passed to it. In common use, i believe an array of strings should suffice, however, I am sure objects could also be passed if needed. The next step is to extend the Enum class with whatever enumerated type you would like, creating static strings for each possible value, and then passing that array in the constructor … as follows
package Enums
{
import Enums.baseEnum.Enum;
public class Colors extends Enum
{
public static const Black:String = ‘Black’;
public static const Blue:String = ‘Blue’;
public static const Green:String = ‘Green’;
public static const Red:String = ‘Red’;
public function EquipmentType(initialValue:String)
{
super(['Black','Blue','Green','Red'],initialValue);
}
}
}
then in use of this program, one can create as many Colors objects they want, and each one may be set using the set value call and the ClassName. Static variable…. i.e.
private var myColor:Colors = new Colors(Colors.Blue);
use myColor.value to get the set value or myColor.value = Colors.XXXXX to set the value.
What does everyone think?
February 23rd, 2009 at 4:26 am
@JoshMcDonald: Once the class is declared as final this implementation of
enumerated types does prove quite useful. The reason the author prevents
further instantiations of the class is to ensure strict adherence to the very
definition of an enumerated type; a type with a fixed number of predefined values.
This “strictness” may seem unnecessary but consider the following scenario. A
method takes a DebitCreditEnum instance as a parameter. Inside the method body the
enum parameter is tested for equality with static constants of the enum class.
Now, if instantiation of an enum were possible, the method could be passed a DebitCreditEnum instance independent of those declared as static constants.
In that case, the test in the method body would fail, but this error would crop
up at run-time and be more difficult to debug compared to a compile-time error.