From AS3 to C#, Part 17: Conditionals, Exceptions, and Iterators
Continuing the series on C# syntax, today we’ll look at the differences an AS3 programmer can expect to encounter when using conditionals (if/else
, switch/case/break/goto
) and exceptions (try/catch/finally/throw
). We’ll also look at iterators, an all-new category for AS3 programmers that empowers us to both iterate however we want and to write coroutines, a kind of lightweight pseudo-thread.
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 simple with what has barely changed at all in C#: if/else
conditionals. These are exactly the same in syntax and meaning in both AS3 and C# with one exception. In AS3 there is a whole range of values that are implicitly converted to true
or false
when put into an if
statement. In C#, you can only use a true
or false
value. For example, the following AS3 code implicitly checks several non-Boolean
values:
if (55) { /* ... */ } if ("hello") { /* ... */ } if (someObject) { /* ... */ }
All of these would yield compiler errors in C#. Instead, we normally just write expressions that result in a boolean value:
if (3 > 2) { /* ... */ } if (someBool) { /* ... */ } if (!isDone) { /* ... */ }
Switch statements are, however, quite a bit more different in C#. In AS3, you’re allowed to “fall through” the case
labels to execute the code in the next one. This is a frequent cause of errors, but also a source of flexibility. Here’s how it looks:
var intValue:int = 1; var result:int; switch (intValue) { case 0: result = 0; case 1: result = 1; case 2: result = 2; } trace(result); // output: 2
Normally, you prevent this with an explicit break
statement in each case
:
var intValue:int = 1; var result:int; switch (intValue) { case 0: result = 0; break; case 1: result = 1; break; case 2: result = 2; break; } trace(result); // output: 1
In C#, you’re only allowed to “fall through” the cases when there is nothing in them. This is useful for grouping how you handle multiple switch values, but less error prone as the missing break
is less likely to be overlooked. In this example, the 0
case is legitimately empty and can omit the break
but the others must include it or face a compiler error:
int intValue = 1; int result; switch (intValue) { case 0: case 1: result = 1; break; case 2: result = 2; break; } Debug.Log(result); // output: 1
In both languages, break
can be replaced by a return
or throw
statement if you want to exit the whole function or jump to a catch
or finally
block rather than just skip to the end of the switch
. However, C# has another option available to you that allows you to regain some of the lost “fall through” power that AS3 has. Rather than simply omitting the break
, you just add a goto case X
or goto default
statement like this:
int intValue = 1; int result = -1; switch (intValue) { case 0: result = 0; goto case 1; case 1: result = 1; goto case 2; case 2: result = 2; goto default; default: break; } Debug.Log(result); // output: 2
This example has restored the total “fall through” behavior of the first AS3 example. It’s more verbose and explicit than the AS3 example, too. The disadvantage is that we have to type more code to explicitly fall through, but the advantage is that it’s harder to accidentally omit a break
and case runtime errors. Another advantage is that we can “fall” to cases that aren’t necessarily the next one, yielding even more flexibility:
int intValue = 1; int result = -1; switch (intValue) { // "fall up" case 0: result = 0; goto default; case 1: result = 1; goto case 0; case 2: result = 2; goto case 1; default: ; } Debug.Log(result); // output: 0
Next up are exceptions. Like if
and else
, they too are very similar between the two languages, but with some key differences too. You throw an exception with throw
and catch it with try
, catch
, and finally
blocks. Here are the basics in AS3:
try { trace("before"); throw new Error("boom"); trace("after"); } catch (err:Error) { trace("caught it, re-throwing..."); throw err; } finally { trace("cleanup"); } /* output: before caught it, re-throwing... cleanup */
C# allows the exact same code with one change. When re-throwing an exception within a catch
block, you don’t need to specify its name:
try { Debug.Log("before"); throw new Error("boom"); Debug.Log("after"); } catch (Exception ex) { Debug.Log("caught it, re-throwing..."); throw; } finally { Debug.Log("cleanup"); } /* output: before caught it, re-throwing... cleanup */
One final note on exceptions in C#: you have to throw either a System.Exception
or an object that derives from it. This is unlike AS3 where you can throw anything you want, including int
, Boolean
, Vector.<String>
, and all manner of other objects.
Lastly for today, let’s talk about an all-new concept in C#: iterators. These are a special type of function that can suspend its operation and be resumed later. When resumed, it’l have its full state restored so it picks up just where it left off. They’re called iterators because they’re often used for that purpose: iterating through some values in a custom way. To illustrate, here’s an iterator function that returns perfect squares:
foreach (var square in PerfetSquares(100)) { Debug.Log(square); } private IEnumerable<int> PerfetSquares(int max) { var cur = 0; for (var i = 0; cur < max; ++i) { cur = i * i; yield return cur; } } /* output: 0 1 4 9 16 25 36 49 64 81 100 */
There are a few parts to the puzzle. First, the function must return either System.Collections.IEnumerable
or System.Collections.Generic.IEnumerable<T>
. Second, the function must include at least one yield return X
statement. This statement suspends the function and returns a value to the caller. Third, the caller must handle the IEnumerable
by repeatedly calling it to resume the function and get the next value. The foreach
loop does this last part for us, but we could do it manually if we wanted more control:
IEnumerable<int> enumerable = PerfetSquares(100); IEnumerator<int> enumerator = enumerable.GetEnumerator(); while (enumerator.MoveNext()) { Debug.Log(enumerator.Current); }
Manually calling MoveNext
like this allows us more flexibility. We don’t need to simply call the iterator function over and over until it’s done. Instead, we can save off the IEnumerator
as a class field variable and call it again elsewhere or later on. If you look at the iterator function as a function that does something in little steps, it becomes a coroutine. These can be useful for all sorts of tasks, such as artificial intelligence actors. Here’s a simple “cat and mouse” game where a “cat” iterator function chases a “mouse” iterator function:
public class CatMouseGame { int catX = 0, catY = 0; int mouseX = 4, mouseY = 8; public CatMouseGame() { var catCR = Cat().GetEnumerator(); var mouseCR = Mouse().GetEnumerator(); while (catCR.MoveNext() && mouseCR.MoveNext()) { Debug.Log( "cat: (" + catX + ", " + catY + "), " + "mouse: (" + mouseX + ", " + mouseY + ")" ); } } private IEnumerable Cat() { var speed = 2; while (catX != mouseX || catY != mouseY) { yield return true; catX += Math.Min(speed, mouseX - catX); catY += Math.Min(speed, mouseY - catY); } yield return false; } private IEnumerable Mouse() { var speed = 1; while (catX != mouseX || catY != mouseY) { yield return true; mouseX += speed * Math.Sign(mouseX - catX); mouseY += speed * Math.Sign(mouseY - catY); } yield return false; } } /* output: cat: (0, 0), mouse: (4, 8) cat: (2, 2), mouse: (5, 9) cat: (4, 4), mouse: (6, 10) cat: (6, 6), mouse: (6, 11) cat: (6, 8), mouse: (6, 12) cat: (6, 10), mouse: (6, 13) cat: (6, 12), mouse: (6, 14) cat: (6, 14), mouse: (6, 14) */
Notice how the iterator functions are called in an interleaved manner: cat, mouse, cat, mouse, etc. We could do this with an arbitrary collection of iterator functions’ enumerators to simulate all the actors in a game, for example. Essentially, we have a tool that allows us to build small, synchronous, lightweight pseudo-threads.
Another thing to notice is that the cat and mouse positions are stored as field variables rather than parameters. It’s certainly possible to take parameters (we did it in the PerfectSquares
example), but we aren’t allowed to take any ref
or out
parameters. This means that the Cat
and Mouse
iterator functions wouldn’t be able to change the positions via ref
parameters, so another means is necessary.
Finally, there is the issue of stopping the iteration. You may have spotted that the PerfectSquares
example simply ends with no return
statement even though it’s declared as not returning void
. The reason this is allowed is that iterators get an exception to the normal rule when they use their first yield return
statement. The compiler simply generates a new statement at the end of the function: yield break
. This can be used elsewhere in the function like you would use return;
in a void
-returning function. Here’s how PerfectSquares
would look if we used yield break
:
private IEnumerable<int> PerfetSquares(int max) { for (var i = 0; true; ++i) { var cur = i * i; if (cur > max) { yield break; } else { yield return cur; } } }
Now let’s sum up for today with a side-by-side comparison of how the two languages handle conditionals, exceptions, and iterators:
//////// // C# // //////// // If-else if (something) // "something" must be boolean { } else { } // Switch switch (something) { // Fall through case 0: // must be empty // Go to another case case 1: goto case 2; // Go to the default case case 2: goto default; // Default case default; // Go to the end of the switch break; } // Execute statements that may throw exceptions try { } // Catch an exception type catch (Exception ex) { // Re-throw the exception throw; } // Execute if exception or not finally { } // Iterator function IEnumerable<int> Range(int min, int max, int step=1) { for (var i = min; i < max; i += step) { // Suspend function and return current value yield return i; } } // Simple loop over iterator function foreach (var cur = Range(2, 10)) { } // Manually call iterator function var enumerable = Range(2, 10); var enumerator = enumerable.GetEnumerator(); enumerator.MoveNext();
///////// // AS3 // ///////// // If-else if (something) { } else { } // Switch switch (something) { // Fall through case 0: // Go to another case // {impossible in AS3} // Go to the default case // {impossible in AS3} // Default case default; // Go to the end of the switch break; } // Execute statements that may throw exceptions try { } // Catch an exception type catch (err:Error) { // Re-throw the exception throw err; } // Execute if exception or not finally { } // Iterator function // {impossible in AS3} // Simple loop over iterator function // {impossible in AS3} // Manually call iterator function // {impossible in AS3} // //
Next time we’ll continue the series by looking at various ways to allocate and clean up resources beyond just new
and the garbage collector. Stay tuned!
Spot a bug? Have a question or suggestion? Post a comment!
#1 by henke37 on November 10th, 2014 ·
Are you sure that AS3 doesn’t have something resembling iterators? I recall the proxy class having something along these lines.
#2 by jackson on November 10th, 2014 ·
Thanks for reminding me that
Proxy
has the ability to mimic an iterator. It’s really awkward to right them and the performance is disastrous, but it can be done and even works withfor-each
andfor-in
loops. A faster, cleaner approach is more likely something like what Honza shows below or a strongly-typed class that looks like aProxy
, but both give up normalfor-each
andfor-in
loop support, among other disadvantages.#3 by Honza BÅ™eÄka on November 10th, 2014 ·
AS3 doesn’t have generators built-in, but they can be simulated:
#4 by jackson on November 10th, 2014 ·
This is a good example of one approach to simulating iterators/generators. Of course, without first-class language support there are numerous issues that come up. One of the problems with a simulation like this is that the function only ends with a
RangeError
being thrown. This is not only slow and awkward to write, but also requires the caller to catch the exception. It also doesn’t allow any of the function’s state to be preserved between calls since each call starts at the beginning of the function. This makes it harder to write an iteration/generator function. But still, for occasional use in the right conditions, a simulation like this may come in handy.