From AS3 to C#, Part 5: Static Classes, Destructors, and Constructor Tricks
Last week’s article mostly covered abstract classes, but this week we’ll discuss an even more abstract type of class: static classes. We’ll also explore C#’s anti-constructor, known as a destructor, and some fancy ways to construct a class. Read on and learn some more class-related features that were never available to us in AS3.
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
Let’s dive right in with the “even more abstract” class: static classes. With abstract classes you can still extend them and then instantiate the derivative class:
abstract class Shape { } class Square : Shape // legal { } new Shape(); // illegal new Square(); // legal
With static classes, you can neither instantiate them nor derive from them. You can never have an instance of one:
static class Shape { } class Square : Shape // illegal { } new Shape(); // illegal new Square(); // illegal
So what are they good for? Well, they’re a good place to put a collection of static functions, variable fields, and properties. Since you can never instantiate one, non-static fields of all types are not allowed. Instance constructors are also not allowed and the class is automatically sealed
. One common static class example is Math
. You’d never want to instantiate “a math”, but it has lots of useful static functions (e.g. Abs
) and fields (e.g. PI
). Here’s how that might be implemented:
public static class Math { // remember that 'const' is automatically static // also, this would surely have more precision public const double PI = 3.1415926; public static double Abs(double value) { return value >= 0 ? value : -value; } } new Math(); // illegal
AS3 has no compiler support for static classes, but you can work around this and implement them at runtime. All you have to do is make your class final
and your constructor always throw an exception:
public final class Math { public static const PI:Number = 3.1415926; public function Math() { throw new Error("Math is static"); } public static function abs(value:Number): Number { return value >= 0 ? value : -value; } } new Math(); // legal, but throws an exception
Next up are destructors. These are the anti-constructor because they are responsible for destroying the class rather than setting it up. Destructors are run by the garbage collector right before the instance has its memory reclaimed. Here’s how they look:
class TemporaryFile { ~TemporaryFile() { // cleanup code goes here } }
All you need to do is add a ~ before the class name. You can only have one and it never takes any access modifiers or parameters. Normally you don’t need to create destructors, but in some cases it can be a helpful way to clean up after a class. In the TemporaryFile
example, it should delete the temporary file from the file system because the garbage collector never will:
using System.IO; class TemporaryFile { public String Path { get; private set; } TemporaryFile(String path) { Path = path; File.Create(path); } ~TemporaryFile() { File.Delete(Path); } } // Create the temporary file TemporaryFile temp = new TemporaryFile("/path/to/temp/file"); // ... use the temporary file // Remove the last reference to the TemporaryFile instance // GC will now collect temp, call the destructor, and delete the file temp = null;
This class now creates a file when it is constructed and deletes the file when all instances are no longer referenced and the garbage collector reclaims its memory. In AS3, there’s no function that is called when your class instance is about to be garbage collected. Instead, you must manually call a pseudo-destructor that’s normally named dispose
or destroy
:
import flash.filesystem; class TemporaryFile { private var _path:String; public function get path(): String { return _path; } public function set path(p:String): void { _path = p; } private var _file:File; function TemporaryFile(path:String) { _path = path; _file = new File(path); var stream:FileStream = new FileStream(); stream.open(_file, FileMode.WRITE); } function dispose(): void { _file.deleteFile(); } } // Create the temporary file var temp:TemporaryFile = new TemporaryFile("/path/to/temp/file"); // ... use the temporary file // Manually call dispose() to delete the temporary file temp.dispose(); // Remove the last reference to the TemporaryFile instance // GC will now collect temp temp = null;
The last topic for today is a constructor trick. We saw before that we can call our base class constructor using the base
keyword in a similar manner to AS3’s super
keyword:
class Polygon { Polygon(int numSides) { } } class Triangle : Polygon { Triangle() : base(3) // call the Polygon constructor { } }
We also saw that we can have more than one constructor using a technique known as “overloading”:
class Vector3 { double X; double Y; double Z; Vector3() { X = 0; Y = 0; Z = 0; } Vector3(double x, double y, double z) { X = x; Y = y; Z = z; } Vector3(Vector3 vec) { X = vec.X; Y = vec.Y; Z = vec.Z; } } Vector3 v1 = new Vector3(); // (0, 0, 0) Vector3 v2 = new Vector3(1, 2, 3); // (1, 2, 3) Vector3 v3 = new Vector3(v2); // (1, 2, 3)
But that often leads to quite a bit of code duplication between the constructors. The version taking three values is the most generic, so let’s just have the other two call that to do their construction:
class Vector3 { double X; double Y; double Z; Vector3() : this(0, 0, 0) { } Vector3(double x, double y, double z) { X = x; Y = y; Z = z; } Vector3(Vector3 vec) : this(vec.X, vec.Y, vec.Z) { } } Vector3 v1 = new Vector3(); // (0, 0, 0) Vector3 v2 = new Vector3(1, 2, 3); // (1, 2, 3) Vector3 v3 = new Vector3(v2); // (1, 2, 3)
Just like with the base()
call, we can use this()
to call other constructors in our own class. Again, AS3 doesn’t have this functionality so we’re forced to work around it by creating static pseudo-constructor functions for each constructor overload and then a helper function normally called init
, setup
, or construct
:
class Vector3 { var x:Number; var y:Number; var z:Number; function Vector3() { init(0, 0, 0); } // pseudo-constructor static function fromComponents(x:Number, y:Number, z:Number) { var ret:Vector3 = new Vector3(); ret.init(x, y, z); return ret; } // pseudo-constructor static function fromVector(Vector3 vec) { var ret:Vector3 = new Vector3(); ret.init(vec.X, vec.Y, vec.Z); return ret; } // helper function function init(x:Number, y:Number, z:Number): void { this.x = x; this.y = y; this.z = z; } } var v1:Vector3 = new Vector3(); // (0, 0, 0) var v2:Vector3 = Vector3.fromComponents(1, 2, 3); // (1, 2, 3) var v3:Vector3 = Vector3.fromVector(v2); // (1, 2, 3)
That wraps things up for today. To summarize, here’s a comparison between C# and AS3 covering everything in this article:
//////// // C# // //////// // Static class public static class MathHelpers { public const double DegreesToRadians = Math.PI / 180.0; public const double RadiansToDegrees = 180.0 / Math.PI; public static double ConvertDegreesToRadians(double degrees) { return degrees * DegreesToRadians; } public static double ConvertRadiansToDegrees(double radians) { return radians * RadiansToDegrees; } } // Class with a destructor class TemporaryFile { public String Path { get; private set; } TemporaryFile(String path) { Path = path; File.Create(path); } // Destructor ~TemporaryFile() { File.Delete(Path); } } // Class with shared constructor code class Vector3 { double X; double Y; double Z; Vector3() : this(0, 0, 0) { } // shared constructor code Vector3(double x, double y, double z) { X = x; Y = y; Z = z; } Vector3(Vector3 vec) : this(vec.X, vec.Y, vec.Z) { } }
///////// // AS3 // ///////// // Static class - runtime only public class MathHelpers { public static const DegreesToRadians:Number = Math.PI / 180.0; public static const RadiansToDegrees:Number = 180.0 / Math.PI; public function MathHelpers() { throw new Error("MathHelpers is static"); } public static function ConvertDegreesToRadians(degrees:Number): Number { return degrees * DegreesToRadians; } public static function ConvertRadiansToDegrees(radians:Number): Number { return radians * RadiansToDegrees; } } // Class with a destructor class TemporaryFile { private var _path:String; public function get path(): String { return _path; } public function set path(p:String): void { _path = p; } private var _file:File; function TemporaryFile(path:String) { _path = path; _file = new File(path); var stream:FileStream = new FileStream(); stream.open(_file, FileMode.WRITE); } // Destructor - must be called manually function dispose(): void { _file.deleteFile(); } } // Class with shared constructor code class Vector3 { var x:Number; var y:Number; var z:Number; function Vector3() { init(0, 0, 0); } static function fromComponents(x:Number, y:Number, z:Number) { var ret:Vector3 = new Vector3(); ret.init(x, y, z); return ret; } static function fromVector(Vector3 vec) { var ret:Vector3 = new Vector3(); ret.init(vec.X, vec.Y, vec.Z); return ret; } // shared constructor code - helper function required function init(x:Number, y:Number, z:Number): void { this.x = x; this.y = y; this.z = z; } }
Next week’s article will continue with even more C# class features that aren’t available in AS3. Stay tuned!
Spot a bug? Have a question or suggestion? Post a comment!
#1 by Kuba Bladek on August 18th, 2014 ·
Keep this up Jackson! I am reading your blog for couple of years and you are the real mentor for me. I am sticking to AS3 for our recent game, but I like to have an eye on the horizon ;)
#2 by Alex H on August 22nd, 2014 ·
Your AS3 Vector3 definition is still invalid, you can’t have multiple constructor definitions since AS3 doesn’t support overloading.
#3 by jackson on August 22nd, 2014 ·
Thanks for spotting this. I’ve updated the article by changing the
Vector3
class to use static pseudo-constructors instead of constructor overloads, as is the common practice to work around this language limitation.#4 by Glantucan on September 7th, 2014 ·
Hi Jackson,
First things first. I’ve been following you for a while now and I really think your blog rocks!
I know you mainly focus on writing about performance, but I really would like to know your insights about good coding practices. For example, when it is actually sane to use static classes and its close relatives singletons, specially in Unity, as typical dependency injection techniques are perticularly involved when dealing with MonoBehaviors.
I know I’m way off the subject here. But it just came to my mind reading this article. I teach programming in a technical school and I usually avoid explaning static classes cause students tend to overuse them when they realize how much work it takes to inject dependencies. This year I start teaching Unity, and I honestly don’t know how to avoid static classes. GameObject.Find() combined with GetComponent() it’s too messy for me.
#5 by jackson on September 7th, 2014 ·
Thanks for the kind words. I don’t usually cover best practices for two reasons. First, it’s a highly subjective subject and I prefer to offer either factual or data-driven information. Second, there’s quite a few other blogs, social feeds, and books about the subject. Still, I may cover some more egregious cases from time to time. I may also change this stance as Unity in particular seems to be prone to poor programming structure, such as the examples your point out.
#6 by Glantucan on September 8th, 2014 ·
I understand it. The subject of whether or not to use singletons is specially polemic.
But without going in to it I would love to see an alternative approach for managing persistent objects and dependency injection in Unity without using a framework. I’v e been searching for it and everybody seems to overuse singlentons or difficult to grasp for beginners frameworks like strangeIO. If you have any pointers on these I’d highly apreciate it.
Thanks anyway :)
#7 by mayorovp on February 3rd, 2015 ·
Never use “destructors” (finalizers). You cannot predict moment of time when it called.
For manual destruction, you should use Dispose() method from IDIsposable interface. For automatic destruction of unmanaged objects you should use SafeHandles.
Sorry for my english.