From AS3 to C#, Part 10: Alternatives to Classes
Now that we’ve finished discussing special 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#!
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 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 0
to the first name in the list—Sunday
—and 1
, 2
, 3
, 4
, 5
, and 6
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 1
through 7
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 1000
. 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 or passing them to a function 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 a comment!
#1 by Glantucan on September 23rd, 2014 ·
One important detail about structures is that is a value type, while classes are reference types. This implies that when a structure is passed as an argument to a function, its value is copied, i.e. a new instance is created and its field values and methods are copied to it. This is pretty important. In Unity Vector3 is a structure, and not knowing that can lead to a lot of head scratching :)
#2 by jackson on September 23rd, 2014 ·
That’s actually the first thing I mentioned about structures:
The example showed how the copying works:
And I mentioned the term “copy by value” when I contrasted with AS3:
However, I never mentioned explicitly that a copy is made when you pass a structure to a function. I’ve updated the article to make this explicit. Thanks for pointing it out!
#3 by Glantucan on September 23rd, 2014 ·
Oops!
I certainly missed those parts, my apologies.
Glad my comment was of use though :)
Cheers!
#4 by Barliesque on October 14th, 2014 ·
One detail about enums that surprised me is the built-in implicit conversion to string.
Rather than this printing the integer value 10, it prints the string “Sunday”
#5 by jackson on October 14th, 2014 ·
Good point. You can actually convert to either the integer value or a string:
Debug.Log
andConsole.WriteLine
happen to useToString
to do the conversion.#6 by Barliesque on October 16th, 2014 ·
I was just coming back to correct myself– “implicit” casts don’t work, but ToString() works. I’ve also just found that Enum’s are equipped with a Parse() function to convert *from* Strings to the enumeration type.
…by the way, this is an *excellent* series you’ve put together here — All the right info for the masses of AS3 programmers making what looks like the inevitable shift to C#/Unity3D — without having to wade through all the beginner programming stuff. I’ve come a long way on my own, but your series has really helped fill in some gaps.
#7 by jackson on October 16th, 2014 ·
Thanks. I’m glad you’re finding it helpful. :)
And that’s a good tip about
Parse()
to convert the other way.#8 by Bernd on July 22nd, 2015 ·
Recently I had to do simple arithmetic operations on enums. Interestingly doing a very simple arithmetic operation on a enum keeps the type, while doing something more, like using the remainder operator % leads to compilation errors, and makes casting back and forth between the enum type and the value type mandatory.
i.e.
Interestingly you can also in C# still set enums to “invalid” values.
#9 by jackson on July 22nd, 2015 ·
Thanks for posting your findings. It’s interesting to see what kinds of arithmetic you can do on the enum and what you can’t. I’ll look into this further and perhaps you’ll see an article further exploring the subject. :)
#10 by Bernd on July 22nd, 2015 ·
sorry i did not get the formatting right.