C # for AS3 developers. Part 5: Static Classes, Destructors, and Tricks for Working with Constructors

Original author: Jackson Dunstan
  • Transfer
  • Tutorial
image

Translation of an article From AS3 to C #, Part 5: Static Classes, Destructors, and Constructor Tricks

Last time we looked at abstract classes, but this week we will discuss an even more abstract type of classes (than abstract classes): static classes. We will also look at C # anti-constructors, which are better known as “destructors”, and, in addition to everything, we will look at some funny tricks when working with class constructors.


Static classes

Let's start today's article with “even more abstract” classes: static classes. When working with abstract classes, you can still extend them and instantiate child classes:

abstract class Shape
{
}
class Square : Shape // legal
{
}
new Shape(); // illegal
new Square(); // legal


When working with static classes, you can neither instantiate nor inherit them. You can never create an instance of such a class:

static class Shape
{
}
class Square : Shape // illegal
{
}
new Shape(); // illegal
new Square(); // illegal


But why would such classes be needed at all? Classes like these can be a good place to store static functions, fields, and properties. And, since you cannot create instances of such classes, it is forbidden to use non-static fields of any data types in them. Constructors of class instances are also forbidden, because a class is automatically equated to sealed classes. A fairly popular example of using such classes is the Math class. You are unlikely to ever need to instantiate this class, but it contains a large number of useful static functions (such as Abs) and fields (such as PI). Here's what an implementation of such a class might look like:

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


In AS3, by default there is no support for static classes at the compilation stage, but you can get around this limitation using checks at the playback stage (run-time). All you have to do is declare the class as final, and always throw an error in the constructor of this class:

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


Destructors

The next item in today's program is destructors, which are “anti-constructors”, because they are responsible for destroying a class, and not for creating it, as is the case with ordinary constructors. Destructors are called by the Garbage Collector just before the object frees the memory it occupies. Here's what they look like:

class TemporaryFile
{
    ~TemporaryFile()
    {
        // cleanup code goes here
    }
}


To create a destructor, just add ~ to the class name. There can be only one destructor, and you cannot use access modifiers with it. Usually, there is no need to create destructors, but in some cases they can be useful as a way to clear resources after using the class. In the example below, the destructor is used to delete a temporary file from the operating system, which in another case will not be deleted, because GC will never do this:

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;


In this example, the TemporaryFile class creates a file in the constructor of the class instance, and deletes the file when there are no references to the class instance and the class is ready to be assembled by GC to free up memory. There are no functions in AS3 that would be called when the class instance is ready to be assembled by GC. Usually, to implement this behavior, you need to manually create and call "pseudo-destructors" (usually called 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;


Tricks when working with designers

The last topic for today will be tricks when working with designers. We have already figured out how to call the base class constructor using the base keyword (similar to using the super keyword in AS3):

class Polygon
{
    Polygon(int numSides)
    {
    }
}
class Triangle : Polygon
{
    Triangle()
        : base(3) // call the Polygon constructor
    {
    }
}


Also, we considered the possibility of creating more than one constructor using "overload":

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)


Usually this method leads to duplication of code inside the constructors. But, because The constructor version, which takes 3 parameters as the most common of all, you can simply call it from 2 other designers:

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)


We can use this () to call other constructors within our class (similar to base (), which allows us to call the constructor of the parent class). And again, in AS3 there was no such functionality by default, so it had to be “emulated” using static pseudo-constructors that called functions like init / setup / contruct on created objects:

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)


We will end here today and, as usual, at the end of the article we will compare the features of working with C # and AS3 described today:

////////
// 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;
    }
}


Also popular now: