From AS3 to C#, Part 4: Abstract Classes and Functions
Continuing from last time, this article begins covering features of C# classes that aren’t in AS3. We’ll begin with abstract classes and functions, which AS3 required workaround code to enforce even at run-time. Today’s article shows you how to use C# to cleanly enforce these at compile-time.
Table of Contents
- From AS3 to C#, Part 1: Class Basics
- From AS3 to C#, Part 2: Extending Classes and Implementing Interfaces
- From AS3 to C#, Part 3: AS3 Class Parity
- From AS3 to C#, Part 4: Abstract Classes and Functions
- From AS3 to C#, Part 5: Static Classes, Destructors, and Constructor Tricks
- From AS3 to C#, Part 6: Extension Methods and Virtual Functions
- From AS3 to C#, Part 7: Special Functions
- From AS3 to C#, Part 8: More Special Functions
- From AS3 to C#, Part 9: Even More Special Functions
- From AS3 to C#, Part 10: Alternatives to Classes
- From AS3 to C#, Part 11: Generic Classes, Interfaces, Methods, and Delegates
- From AS3 to C#, Part 12: Generics Wrapup and Annotations
- From AS3 to C#, Part 13: Where Everything Goes
- From AS3 to C#, Part 14: Built-in Types and Variables
- From AS3 to C#, Part 15: Loops, Casts, and Operators
- From AS3 to C#, Part 16: Lambdas and Delegates
- From AS3 to C#, Part 17: Conditionals, Exceptions, and Iterators
- From AS3 to C#, Part 18: Resource Allocation and Cleanup
- From AS3 to C#, Part 19: SQL-Style Queries With LINQ
- From AS3 to C#, Part 20: Preprocessor Directives
- From AS3 to C#, Part 21: Unsafe Code
- From AS3 to C#, Part 22: Multi-Threading and Miscellany
- From AS3 to C#, Part 23: Conclusion
First off, I forgot one feature of AS3’s classes in the previous three articles. Let’s briefly discuss static static initializers, also known as class initializers, class constructors, or static constructors. This is a function that is called automatically when the class’ static fields need to be initialized prior to the class being used. Here’s how it looks in AS3:
class Person { private static var NEXT_ID:int; private var id:int; // static initializer: { NEXT_ID = 1; } // instance constructor function Person() { id = NEXT_ID++; } }
Static initializers aren’t commonly used because you can declare and initialize your fields at the same time (private static var NEXT_ID:int = 1;
), but can be useful if the initialization requires more complex logic. In any case, here’s the equivalent in C#:
class Person { private static int NextID; private int id; // static initializer: static Person() { NextID = 1; } // instance constructor Person() { id = NextID++; } }
Static initializers in C# are called a “static constructor”. It’s like a constructor for the class rather than a constructor for an instance of the class. It’s syntax is just like that of an instance constructor, except that it has the static
keyword first, it never has an access modifier, and it doesn’t take any parameters.
Now let’s talk about abstract classes. These are classes that can’t be instantiated directly. To create one, you need to extend the abstract class with a non-abstract class and instantiate the extending class. AS3 lacks this feature at compile-time, but there is a common run-time workaround:
class ExtrudedShape { private var depth:int; protected static const HIDDEN_KEY:Object = {}; function ExtrudedShape(ABSTRACT:Object, depth:int) { if (ABSTRACT != HIDDEN_KEY) { throw new ArgumentError("ExtrudedShape is an abstract class"); } this.depth = depth; } function get area(): int { return 0; } function get volume(): int { return depth * area; } }
With this workaround you can still write code that creates an ExtrudedShape
directly and it will compile just fine:
var shape:ExtrudedShape = new ExtrudedShape(null, 3);
At runtime, the first argument will be checked against the private const
Object
that anyone outside of the class and its derivatives couldn’t possibly have access to. The ArgumentError
will then be thrown and the caller will not get an instance of the ExtrudedShape
. A derivative class, on the other hand, has access to HIDDEN_KEY
and can pass it to the constructor:
class ExtrudedCircle extends ExtrudedShape { function ExtrudedCircle(depth:int) { super(HIDDEN_KEY, depth); } }
This is an effective strategy for implementing abstract classes at run-time, but C# offers a way to enforce this at compile-time:
abstract class ExtrudedShape { private int depth { get; private set; } ExtrudedShape(int depth) { this.depth = depth; } int Area { get { return 0; } } int Volume { get { return depth * Area; } } }
Note the use of the abstract
keyword when declaring the class. This tells the compiler not to let anyone directly instantiate the class. There’s no need for any of the runtime workaround that AS3 requires. Derivative classes don’t need any HIDDEN_KEY
and look just like classes extending any other non-abstract classes:
class ExtrudedCircle : ExtrudedShape { ExtrudedCircle(int depth) : base(depth) { } }
Similar to abstract classes are abstract functions. These are used when you want your abstract class to not define a function but instead mandate that classes extending it must implement the function. There is, again, no way to do this in AS3 at compile-time. Instead, it can be worked around at runtime:
class ExtrudedShape { private var depth:int; protected static const HIDDEN_KEY:Object = {}; function ExtrudedShape(ABSTRACT:Object, depth:int) { if (ABSTRACT != HIDDEN_KEY) { throw new ArgumentError("ExtrudedShape is an abstract class"); } this.depth = depth; } function get area(): int { throw new Error("'get area' is an abstract function"); return 0; } function get volume(): int { return depth * area; } }
Here the ExtrudedShape
class doesn’t want to define the get area
function since it really doesn’t know anything about that. So its version of get area
immediately throws an exception. This makes the function effectively abstract at run-time, but there is again no compile-time checking. The following derivative class compiles just fine without implementing the get area
function:
class ExtrudedCircle extends ExtrudedShape { }
In C#, we simply use the abstract
keyword:
abstract class ExtrudedShape { private int depth { get; private set; } ExtrudedShape(int depth) { this.depth = depth; } abstract public int Area { get; } int Volume { get { return depth * Area; } } } class ExtrudedCircle : ExtrudedShape { private int area; override int Area { get { return area; } } }
The same keyword would be used for non-getter functions, too:
abstract class GameEntity { abstract void TakeDamage(int damage); } class Enemy : GameEntity { int health; override void TakeDamage(int damage) { health -= damage; } }
That wraps up abstract classes and functions for today, as well as static initializers. To summarize, here’s a comparison between C# and AS3 covering everything in this article:
//////// // C# // //////// // Abstract class abstract class GameEntity { private static int NextID; protected int health; int id; static GameEntity() { NextID = 1; } GameEntity(int health) { this.health = health; this.id = NextID++; } // Abstract property bool Friendly { abstract get; } // Abstract function abstract void TakeDamage(int amount) { } } // Non-abstract ("concrete") class class Enemy : GameEntity { Enemy(int health) : base(health) { } // Implemented abstract property override bool Friendly { get { return false; } } // Implemented abstract function override void TakeDamage(int amount) { health -= amount; } }
///////// // AS3 // ///////// // Abstract class - only enforced at run-time class GameEntity { private static var NEXT_ID:int; protected static const HIDDEN_KEY:Object = {}; protected var health:int; var id:int; // Static initializer { NEXT_ID = 1; } function GameEntity(ABSTRACT:Object, health:int) { if (ABSTRACT != HIDDEN_KEY) { throw new ArgumentError("GameEntity is abstract"); } this.health = health; this.id = NEXT_ID++; } // Abstract property/getter - only enforced at run-time function get friendly(): Boolean { throw new Error("'get friendly' is abstract"); return false; } // Abstract function - only enforced at run-time function takeDamage(amount:int): void { throw new Error("takeDamage is abstract"); } } // Non-abstract ("concrete") class class Enemy extends GameEntity { function Enemy(health:int) { super(HIDDEN_KEY, health); } // Implemented abstract property override function get friendly(): Boolean { return false; } // Implemented abstract function override function takeDamage(amount:int): void { health -= amount; } }
Next time we’ll continue with C# class features that aren’t available in AS3. Stay tuned!
Spot a bug? Have a question or suggestion? Post a comment!
#1 by henke37 on August 11th, 2014 ·
Is it mandatory to mark a class as abstract if it contains abstract methods?
#2 by jackson on August 11th, 2014 ·
Yes, because otherwise you could instantiate a non-abstract class that has an abstract function. That is, you could call a function that doesn’t exist. For example:
If you want to allow this behavior, you’ll need to make the function non-abstract and provide a function body that—like the AS3 version—provides a “default” version of the function for when deriving classes (if any) don’t extend it:
#3 by Merlin on August 13th, 2014 ·
Abstract class is very similar to the Interface.
#4 by Gleb Glyaga on August 13th, 2014 ·
What’s the difference from interfaces?
I assume you can use interfaces in AS3 for that purposes and no workarounds needed.
#5 by jackson on August 13th, 2014 ·
C# also has interfaces (see part two of the series), but they serve a slightly different purpose from abstract classes. Interfaces are not allowed to have any variable fields or any defined functions, but abstract classes can. In the example at the end,
GameEntity
has ahealth
variable and a constructor that sets it. Neither the variable nor the constructor are allowed in an interface.One downside to abstract classes is that both AS3 and C# lack support for multiple inheritance, meaning you can only extend one class. However, both languages let you partially work around this by allowing you to implement as many interfaces as you want.
#6 by VirtualMaestro on August 29th, 2014 ·
Hi,
you have a little mistake in code i guess (super) isn’t it ?
#7 by jackson on August 29th, 2014 ·
I’ve updated the article with a fix. Thanks for letting me know!
#8 by Iurii Pavliuk on January 7th, 2015 ·
For some reason following code doesn’t work:
Instead I’ve tried this one and everything is fine:
#9 by jackson on January 7th, 2015 ·
I’ve updated the article to fix this. The
abstract
keyword is now correctly on the property, not theget
. Thanks for letting me know!