From AS3 to C#, Part 5: Static Classes, Destructors, and Constructor Tricks

Datetime:2016-08-23 02:55:52          Topic: ActionScript  C#           Share

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.

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 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);
	}
 
	function Vector3(x:Number, y:Number, z:Number)
	{
		init(x, y, z);
	}
 
	function Vector3(Vector3 vec)
	{
		init(vec.X, vec.Y, vec.Z);
	}
 
	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 = new Vector3(1, 2, 3); // (1, 2, 3)
var v3:Vector3 = new Vector3(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);
	}
 
 
	function Vector3(x:Number, y:Number, z:Number)
	{
		init(x, y, z);
	}
 
 
 
	function Vector3(Vector3 vec)
	{
		init(vec.X, vec.Y, vec.Z);
	}
 
	// 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 acomment !





About List