December 31, 2006

AS3 Casting Issues

In most object oriented languages you can cast an object to something more specific in order to be able to call methods particular to the type it was cast as. This frequently comes up when you have a rich class hierarchy and for some reason can't use polymorphism to separate out behavior or more commonly have a factory like method that needs to take in a bunch of different object types but always return the same type. Since AS3 doesn't support method overloading you can't easily separate out the different object types into specialized methods and instead end up with a big if then else block.

My issue with AS3 comes in with the fact that in such as if then else block you can't easily cast primitive objects without getting some funky behavior or without needing to wrap your cast in order to detect errors. I'm sure that there are some techniques out there to avoid this issue entirely, but having now seen it come up more than once I figured I'd jot down my observations on the issue.

AS3 defines a number of top-level or global functions that mirror primitive AS3 objects types. These include String(), Array(), and Date() (currently undocumented) among others. Because these top-level functions exist, they take precedence over a cast. Consider this method.

public function asString(random:Object):String {
    return String(random);
}

public function asCustom(random:Object):Custom {
    return Custom(random);
}

While the two methods look like they would do the same thing, asString is actually calling a function, while asCustom is doing a cast. AS3 provides the as operator as a way to get around this behavior. The problem is that as won't throw casting errors, instead it just silently returns null. Maybe that's fine for what you need, but it just rubs me the wrong way.

I don't want to always have to check for null because in some cases I know that shouldn't be the case, but I also don't want null introduced because of a bug in the calling code. In my mind tracking down a bug from a class cast error is very different then trying to track down a null pointer error especially since the null could percolate further along in the code compared to the class cast error which would happen at the exact spot the cast was attempted.

The other annoying thing about the difference between cast and as is the trick it can play on you in the debugger especially if you are casting something to a String.

public function testCast():void {
    var a:String;
    a = String(null);
    trace(a);
    trace(a is String);
    a = null as String;
    trace(a);
    trace(a is String);
}
// Output:
// null
// true
// null
// false

In both cases the trace of the variable prints out null, but in the first case it is the actual string null, while in the second it is a null value. As I said before my main issue with as is that it doesn't behave like a true cast.

public function testCastError():void {
    var a:Object = new Object();
    var b:String;
    b = String(a);
    trace(b);
    trace(b is String);
    b = a as String;
    trace(b);
    trace(b is String);
    var c:UIComponent;
    c = UIComponent(a);
}
// Output:
// [object Object]
// true
// null
// false
// TypeError: Error #1034: Type Coercion failed: cannot convert Object@ef409c1 to mx.core.UIComponent.

In all three cases above the Object can't be successfully cast to the target type String and UIComponent. In the first case String() doesn't do a cast, instead we get a string representation of the object. In the second case since the types aren't compatibly as silently returns null. In the third we get what I consider the correct behavior which is a class cast error. Since as has this silent behavior you need to check for null both before and after if you want to distinguish between a null value and an incompatible cast.

In general avoid situations where you need to cast primitive objects or cast things at all, but if you do know that casting isn't always casting in AS3.

Tags: as3 casting flex