Mastering C# and Unity3D

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 structs and enums 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#!

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:

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:

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:

Notice how isWeekday incorrectly assumes that day will be one of the values in the pseudo-enumeration. In fact, it can be anyuint 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:

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:

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 existpublicclass Vector2
{publicvar x:Number;publicvar y:Number;privateconstint NextID =1;privateint id = NextID++;public function get magnitude: Number
{returnreturn 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 datepublic function clone(): Vector2
{returnnew Vector2(x, y);}}// Construct with the 'new' operatorvar a:Vector2 =new Vector2(10, 20);// Declare another vector// Create a copy by calling clone()// Assign the copy to the new variablevar 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) vectorvar c:Vector2;// Declare a vector and assign another to it without calling clone()// It will be a reference to the same vectorvar 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:

Comments

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 :)

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.

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.

enum Days {Monday,Tuesday,Wednesday,Thursday,Friday,Saturday, Sunday};
Days today = Days.Monday;
print (today);// "Monday"// doing simple arithmetic operation keeps the type
Days tomorrow = today+1;
print(tomorrow);// "Tuesday"// while this will fail at compilation time
Days nextWeek =(today +7)%7;// to make it work, you have to cast alway to the right type
Days nextWeek =(Days)((int)(today +7)%7);
print(nextWeek);// "Monday"

Interestingly you can also in C# still set enums to “invalid” values.

// causes an error at compilation time
Days today =7;//does not work, because of wrong type//while these 2 versions work
Days today = Days.Monday+7;
print(today);// "7"// or
Days today =(Days)7;
print(today);// "7"

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. :)