From AS3 to C#, Part 10: Alternatives to Classes

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

Now that we’ve finished discussingspecial functions, we can move on to two alternatives to classes: structures and enumerations. These are commonly called struct s and enum s as most languages use those keywords, including C#. Of course we’ll talk about how to mimic these in AS3, too. Read on to learn about these two important types of data structures available in C#!

Let’s start by talking about enumerations. These are a way to give a type to a collection of constant integer values. Here’s an example:

// Use the enum keyword instead of class
enum Day
{
	// List your names in order
	Sunday,
	Monday,
	Tuesday,
	Wednesday,
	Thursday,
	Friday,
	Saturday
}

Notice that it looks like a class except that its contents are just the names of the constant integer values. The Day enumeration would typically go into a Day.cs file. The compiler will automatically give the value to the first name in the list— Sunday —and , , , , , and to Monday , Tuesday , Wednesday , Thursday , Friday , and Saturday , respectively. If you want to change that, you can specify a new starting value:

enum Day
{
	Sunday = 1,
	Monday,
	Tuesday,
	Wednesday,
	Thursday,
	Friday,
	Saturday
}

Now the values will be through in the same order. If you want to customize further, you can give values to more than one:

enum Day
{
	Sunday = 10,
	Monday = 20,
	Tuesday = 30,
	Wednesday = 40,
	Thursday = 50,
	Friday = 60,
	Saturday = 70
}

You can also specify what type of integer each value should be stored as like so:

enum Day : byte
{
	Sunday = 10,
	Monday = 20,
	Tuesday = 30,
	Wednesday = 40,
	Thursday = 50,
	Friday = 60,
	Saturday = 70
}

Your options include byte , sbyte , short , ushort , int , uint , long , or ulong . You’re not allowed to use char since that’s supposed to be used for characters only, not integers. We’ll cover types more in depth in a forthcoming article.

Now that the Day type has been defined, you can use it like this:

Day d = Day.Monday;

The d variable will have the type Day , not byte . You can’t just assign any integer value to it, even if that value is in the enumeration. For example, this will give a compiler error:

Day d = 10;

This means that your interfaces can be clearer about what they expect and their callers can be clearer about what they’re passing:

bool IsWeekendDay(Day d)
{
	return d == Day.Saturday || d == Day.Sunday;
}
 
IsWeekendDay(Day.Sunday); // true
IsWeekendDay(Day.Thursday); // false

To see what this would all look like without enumerations, we need only look at AS3. Here’s the common strategy to mimic enumerations in AS3, as employed by many interfaces like Array.sort :

class Day
{
	public static const SUNDAY:uint = 10;
	public static const MONDAY:uint = 20;
	public static const TUESDAY:uint = 30;
	public static const WEDNESDAY:uint = 40;
	public static const THURSDAY:uint = 50;
	public static const FRIDAY:uint = 60;
	public static const SATURDAY:uint = 70;
}
 
var day:uint = Day.MONDAY; // 10

A pseudo-abstract class may be used to emphasize that Day is just supposed to be a container for static data. AS3 has no integer types except the 32-bit int and uint , so using byte or any of the other types is impossible.

Users of the pseudo-enumeration must know if the type is int or uint and use it directly, rather than using Day . Day variables are not a Day , just a uint . This leads to relatively less clear and more error-prone interfaces:

function isWeekendDay(day:uint): Boolean
{
	return day == Day.SATURDAY || day == Day.SUNDAY;
}
 
function isWeekday(day:uint): Boolean
{
	return !isWeekendDay(day);
}
 
isWeekendDay(Day.SUNDAY); // true
isWeekendDay(Day.THURSDAY); // false
isWeekendDay(1000); // false
 
isWeekday(Day.SUNDAY); // false
isWeekday(Day.THURSDAY); // true
isWeekday(1000); // true

Notice how isWeekday incorrectly assumes that day will be one of the values in the pseudo-enumeration. In fact, it can be any uint including . This leads to isWeekday(1000) returning true . Even if corrected, the error is still there at compile time. For many functions, it’s quite difficult to handle these sorts of errors that should really just be caught at compile time.

Now let’s move on to structures. These are just like classes but with one major difference: assigning them makes a copy rather than sharing a reference to the same instance. Here’s how that works:

// Use the struct keyword instead of class
public struct Vector2
{
	// Variable fields are OK
	public double X;
	public double Y;
 
	// Private variables and constants are OK, too
	private const int NextID = 1;
	private int id = NextID++;
 
	// Properties are OK
	public double Magnitude
	{
		get { return Math.Sqrt(X*X + Y*Y); }
	}
 
	// Non-default constructors are OK
	public Vector2(double x, double y)
	{
		X = x;
		Y = y;
	}
 
	// Methods are OK
	public void Add(Vector2 other)
	{
		X += other.X;
		Y += other.Y;
	}
}
 
// Call the two-parameter constructor
Vector2 a = new Vector2(10, 20);
 
// Declare and assign a copy
Vector2 b = a;
 
// Call a method that changes the structure
b.Add(a);
 
// The structure it was copied from is unchanged
a.X; // still 10
 
// Declare without using the 'new' operator
// X and Y get their default values: 0
Vector2 c;

Structures have less overhead than classes and can be very useful for optimization when appropriately used. They should always be small objects that can live with the limitations that are placed on structures. These include the inability to derive from any class or structure and the inability to declare a default constructor. You can still implement interfaces, though. You just use the familiar colon ( : ) after the structure’s name:

interface Identifiable
{
	int GetID();
}
 
struct Vector2 : Identifiable
{
	// {snip rest of the structure's body}
 
	public int GetID()
	{
		return id;
	}
}

In AS3, we need to work around the lack of structures. Copy “by value” rather than “by reference” is only supported in AS3 for certain basic types like int and Boolean . Anything else, including Array , String , and Vector , results in a reference copy. We therefore need to make explicit copies like so:

// Use 'class' since 'enum' doesn't exist
public class Vector2
{
	public var x:Number;
	public var y:Number;
 
	private const int NextID = 1;
	private int id = NextID++;
 
	public function get magnitude: Number
	{
		return return Math.sqrt(x*x + y*y);
	}
 
	public function Vector2(x:Number, y:Number)
	{
		this.x = x;
		this.y = y;
	}
 
	public function add(other:Vector2): void
	{
		x += other.x;
		y += other.y;
	}
 
	// Make sure to define and keep a clone() function up to date
	public function clone(): Vector2
	{
		return new Vector2(x, y);
	}
}
 
// Construct with the 'new' operator
var a:Vector2 = new Vector2(10, 20);
 
// Declare another vector
// Create a copy by calling clone()
// Assign the copy to the new variable
var b:Vector2 = a.clone();
 
// Call a method that modifies the new vector
b.Add(a);
 
// The original vector is unchanged
a.x; // still 10
 
// Declare a vector without using the 'new' operator
// It will be null, not a (0,0) vector
var c:Vector2;
 
// Declare a vector and assign another to it without calling clone()
// It will be a reference to the same vector
var d:Vector2 = a;
 
// Changing one of them changes the other
d.x = 100;
a.x; // 100 now

In AS3 you just need to be much more careful to always create, maintain, and call methods like clone above. It’s very error-prone, but can be done if you’re diligent. It will also require the full resources that classes use, such as GC allocation and collection, rather than getting any of the optimizations that structures can provide.

In practice, most of your data structures will still be classes and interfaces like in AS3. Structures and enumerations are there for those cases where neither fits very well. As we’ve seen, it’s possible to work around them in AS3 when they’re not available.

Finally, let’s look at a quick comparison summarizing structures and enumerations in both languages:

////////
// C# //
////////
 
// Structure
struct Vector2
{
	int X;
	int Y;
}
 
 
 
 
 
 
 
 
 
// Create structure without 'new' operator
Vector2 a;
a.X = 10;
a.Y = 20;
 
// Create structure with 'new' operator
Vector2 b = new Vector2(1, 2);
 
// Create structure by assignment
Vector2 c = a;
c.X = 100;
 
// Enumeration
enum EntityType
{
	// Enumeration values
	Player,
	Enemy,
	NPC
}
 
 
 
 
 
 
// Function taking enumeration
bool CanAttack(EntityType type)
{
	return type == EntityType.Enemy;
}
 
// Declare and assign an enumeration
EntityType playerType = EntityType.Player;
 
// Pass an enumeration
CanAttack(playerType);
/////////
// AS3 //
/////////
 
// Structure - class with clone() only
class Vector2
{
	var x:uint;
	var y:uint;
 
	function clone(): Vector2
	{
		var ret:Vector2 = new Vector2();
		ret.x = x;
		ret.y = y;
		return ret;
	}
}
 
// Create structure without 'new' operator
// {impossible, defaults to null}
 
 
 
// Create structure with 'new' operator
var b:Vector2 = new Vector2(1, 2);
 
// Create structure by assignment - requires clone() call
Vector2 c = b.clone();
c.x = 100;
 
// Enumeration - pseudo abstract class only
class EntityType
{
	// Enumeration values
	static const PLAYER:uint = 0;
	static const ENEMY:uint = 1;
	static const NPC:uint = 2;
 
	function EntityType()
	{
		throw new Error("EntityType is abstract");
	}
}
 
// Function taking enumeration - takes uint instead
function canAttack(type:uint): Boolean
{
	return type == EntityType.ENEMY;
}
 
// Declare and assign an enumeration - uint instead
var playerType:uint = EntityType.PLAYER;
 
// Pass an enumeration
canAttack(playerType);

Next week we’ll move on to generics and see how we can harness the power of Vector.<T> in our own classes. Stay tuned!

Spot a bug? Have a question or suggestion? Post acomment !





About List