From AS3 to C#, Part 7: Special Functions

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

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.

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 acomment !





About List