From AS3 to C#, Part 7: Special Functions
Last week we discussed extension methods and virtual functions and today we’ll continue with more special kinds of C# functions. We’ll cover operator overloading, out parameters and reference parameters.
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
First up, let’s talk about out
parameters. These are parameters of a function that are used as part of the function’s output in addition to its return value. Assigning to that parameter actually changes the caller’s variable. It works like this:
void DivideAndMod(double numerator, double denominator, out double quotient, out double modulus) { quotient = numerator / denominator; modulus = numerator % denominator; } void Foo() { double quotient; double modulus; DivideAndMod(3, 2, out quotient, out modulus); Debug.Log(quotient + ", " + modulus); // prints: 1.5, 0.5 }
It’s a great way to return more than one value from a function without needing to use some kind of inefficient wrapper for the return types. All we have to do is use the out
keyword before the parameter names when we declare the function and when we call the function. That way we’re very explicit that certain parameters are going to be modified and there are no surprises.
In AS3, we need to work around the lack of out
parameters with one of those inefficient wrappers. Creating a class is one common way:
class QuotientAndModulus { var quotient:Number; var modulus:Number; } function divideAndMod(numerator:Number, denominator:Number): QuotientAndModulus { var ret:QuotientAndModulus = new QuotientAndModulus(); ret.quotient = numerator / denominator; ret.modulus = numerator % denominator; return ret; } function foo(): void { var ret:QuotientAndModulus = divideAndMod(3, 2); trace(ret.quotient + ", " + ret.modulus); // prints: 1.5, 0.5 }
The inefficiency here is on three fronts. First, the programmer needs to spend time typing out a new class. Second, the class must be stored in the SWF or SWC, bloating its size by a little bit. Do this enough and it’ll add up. Third, and most importantly, memory must be allocated for the QuotientAndModulus
instance which is immediately released for the garbage collector to (slowly) clean up. This leads the AS3 programmer to try pooling QuotientAndModulus
objects to re-use them and avoid the GC, an incredibly tedious process. All of this is unnecessary in C# due to out
parameters.
Very similar to out
parameters in C# are ref
parameters. These are also parameters that the function can assign to change the caller’s variable, but they have an additional requirement: they must be initialized first. This is because out
parameters are just meant to hold a function’s output, but ref
parameters are meant as output and input. Let’s look at an example:
void CapStringLength(ref String str, int maximumLength) { if (str.Length > maximumLength) { str = str.Substring(0, maximumLength); } } void Foo() { String str = "Super Long"; CapStringLength(ref str, 5); Debug.Log(str); // prints: Super }
Syntactically, the only real difference here is that we use the ref
keyword instead of out
. We also had to initialize str
or we’d get a compiler error. Otherwise, the two types of parameters are very similar.
As for AS3, we would again have to work around the lack of ref
parameters by using some kind of wrapper. Let’s use a class again:
class StringWrapper { var str:String; } function capStringLength(wrapper:StringWrapper, maximumLength:int): void { if (wrapper.str.Length > maximumLength) { wrapper.str = wrapper.str.substr(0, maximumLength); } } function Foo(): void { var wrapper:StringWrapper = new StringWrapper(); wrapper.str = "Super Long"; capStringLength(wrapper, 5); trace(wrapper.str); // prints: Super }
For today’s last topic, let’s discuss operator overloading. These are functions that are called when you use regular operators on classes you’ve defined. Math-related classes are a common place to use them:
public class Point2 { public double X; public double Y; // a binary operator public static Point2 operator +(Point2 a, Point2 b) { Point2 ret = new Point2(); ret.X = a.X + b.X; ret.Y = a.Y + b.Y; return ret; } // a unary operator public static Point2 operator -(Point2 a) { Point2 ret = new Point2(); ret.X = -a.X; ret.Y = -a.Y; return ret; } } void Foo() { Point2 a = new Point2(1, 2); Point2 b = new Point2(10, 20); Point2 sum = a + b; Debug.Log(sum.X + ", " + sum.Y); // prints: 11, 22 Point2 sumNegated = -sum; Debug.Log(sumNegated.X + ", " + sumNegated.Y); // prints: -11, -22 }
The overloaded operator is simply a static function whose name is operator X
where X
is the operator you want to overload. Calling it is done implicitly when you use normal operators like +
or -
, not explicitly like with normal functions.
Here are all the unary and binary operators you can overload in C#:
- Unary: +, -, !, ~, ++, ––, true, false
- Binary: +, -, *, /, %, &, |, ^, <<, >>, ==, !=, <, >, <=, >=
In AS3, there are no operator overloads. Instead, it’s common to create normal functions with names that sound like operators:
public class Point2 { public var x:Number; public var y:Number; // a binary "operator" public static function plus(a:Point2, b:Point2): Point2 { var ret:Point2 = new Point2(); ret.x = a.x + b.x; ret.y = a.y + b.y; return ret; } // a unary "operator" public static function negate(a:Point2): Point2 { var ret:Point2 = new Point2(); ret.x = -a.x; ret.y = -a.y; return ret; } } function foo(): void { var a:Point2 = new Point2(1, 2); var b:Point2 = new Point2(10, 20); var sum:Point2 = Point2.plus(a, b); trace(sum.x + ", " + sum.y); // prints: 11, 22 var sumNegated:Point2 = Point2.negate(sum); trace(sumNegated.x + ", " + sumNegated.y); // prints: -11, -22 }
This concludes today’s topics but we’ll summarize with a comparison between C# and AS3 covering extension methods and virtual functions from this article:
//////// // C# // //////// class Point2 { double X; double Y; // a binary operator static Point2 operator +(Point2 a, Point2 b) { Point2 ret = new Point2(); ret.X = a.X + b.X; ret.Y = a.Y + b.Y; return ret; } // a unary operator static Point2 operator -(Point2 a) { Point2 ret = new Point2(); ret.X = -a.X; ret.Y = -a.Y; return ret; } // out parameter void GetMagnitudes(out double mag, out double magSquared) { magSquared = (X * X) + (Y * Y); mag = Math.Sqrt(magSquared); } // ref parameter static void MakeUnitVector(ref double x, ref double y) { double magSquared = (x * x) + (y * y); if (magSquared != 1) { double mag = Math.Sqrt(magSquared); x /= mag; y /= mag; } } }
///////// // AS3 // ///////// class Point2 { var x:Number; var y:Number; // a binary "operator" static function plus(a:Point2, b:Point2): Point2 { var ret:Point2 = new Point2(); ret.x = a.x + b.x; ret.y = a.y + b.y; return ret; } // a unary "operator" static function negate(a:Point2): Point2 { var ret:Point2 = new Point2(); ret.x = -a.x; ret.y = -a.y; return ret; } // Helper for pseudo-out parameter class Magnitudes { var mag:Number; var magSquared:Number; } // pseudo-out parameter function getMagnitudes(): Magnitudes { var ret:Magnitudes = new Magnitudes(); ret.magSquared = (X * X) + (Y * Y); ret.mag = Math.Sqrt(magSquared); return ret; } // pseudo-ref parameter static function makeUnitVector(point:Point2): void { var magSquared:Number = (point.x * point.x) + (point.y * point.y); if (magSquared != 1) { var mag:Number = Math.Sqrt(magSquared); point.x /= mag; point.y /= mag; } } }
Next week we’ll discuss even more kinds of special functions in C#. Stay tuned!
Spot a bug? Have a question or suggestion? Post a comment!
#1 by Tommy on September 1st, 2014 ·
Since you said “These are functions that are called when you use regular operators on classes you’ve defined” im wondering if declaring all your operators overload in a single public class would make them available for the whole project at once?
#2 by jackson on September 1st, 2014 ·
I didn’t mention it in the article, but you can only define overloaded operators in the class they’re for. Otherwise you’ll get an error saying either “One of the parameters of a binary operator must be the containing type” or “One of the parameters of a unary operator must be the containing type”.
That’s not so bad though. You’ll normally have
using X;
andusing Y;
statements to get access to the classes in theX
andY
namespaces, which will get their operator overloads since they must be defined in those classes. The only time there is confusion is with extension methods, as mentioned in the last article. Since they are defined in another class, you may not have visibility to that class’ namespace. For example, if youusing X;
and the extension methods are in theY
namespace, you’ll get a compiler error that looks like the methods aren’t there at all.#3 by VirtualMaestro on September 8th, 2014 ·
Hi! You have one mistake in row
“Unary: +, -, !, ~, ++, –, true, false”
I guess you wanted to write
“Unary: +, -, !, ~, ++, –-, true, false”
Thanks
#4 by jackson on September 8th, 2014 ·
Thanks for letting me know. It seems like WordPress merged them into an mdash. I’ve updated the article with a fix.