From AS3 to C#, Part 8: More Special Functions

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

Today we’ll continue talking about special types of functions in C#. Specifically, today’s article will cover indexers, explicit and implicit conversions, and variable numbers of arguments (“var args”).

Let’s dive right in and discuss indexers. These are special functions that allow you to essentially overload the [] operator. Instances of your class can therefore be “indexed into” just like an array. This can be handy not only for some of the built-in classes like List that want to mimic an array, but also for your own types. For example, you might use them to virtually index two consecutive List instances:

public class ListBridge
{
	private List<String> list1;
	private List<String> list2;
 
	public ListBridge(List<String> list1, List<String> list2)
	{
		this.list1 = list1;
		this.list2 = list2;
	}
 
	public String this[int index]
	{
		get
		{
			if (index < list1.Count)
			{
				return list1[index];
			}
			else
			{
				return list2[index - list1.Count];
			}
		}
		set
		{
			if (index < list1.Count)
			{
				list1[index] = value;
			}
			else
			{
				list2[index - list1.Count] = value;
			}
		}
	}
 
	// not required, just handy
	public int Count
	{
		get
		{
			return list1.Count + list2.Count;
		}
	}
}
 
List<String> list1 = new List<String>();
list1.Add("one");
list1.Add("two");
list1.Add("three");
List<String> list2 = new List<String>();
list2.Add("four");
list2.Add("five");
ListBridge bridge = new ListBridge(list1, list2);
for (int i = 0; i < bridge.Count; ++i)
{
	Debug.Log(i + ": " + bridge[i]);
}
 
// prints...
0: one
1: two
2: three
3: four
4: five

Notice how the indexer ( public String this[int index] ) is implemented very similarly to the property ( public int Count ) as they both can have a get and set function and their set functions both have access to an implicitly-declared value parameter.

If you want to implement this in AS3, you have a couple of options. First, you could suffer the performance hit and loss of type safety and create a dynamic class that extends Proxy class and override some functions:

public dynamic class ListBridge extends Proxy
{
	private var list1:Vector.<String>;
	private var list2:Vector.<String>;
 
	public function ListBridge(list1:Vector.<String>, list2:Vector.<String>)
	{
		this.list1 = list1;
		this.list2 = list2;
	}
 
	override flash_proxy function getProperty(index:*): *
	{
		if (index < list1.length)
		{
			return list1[index];
		}
		else
		{
			return list2[index - list1.length];
		}
	}
 
	override flash_proxy function setProperty(index:*, value:*): void
	{
		if (index < list1.length)
		{
			list1[index] = value;
		}
		else
		{
			list2[index - list1.length] = value;
		}
	}
 
	public function get length(): uint
	{
		return list1.length + list2.length;
	}
}
 
var list1:Vector.<String> = new Vector.<String>();
list1.push("one");
list1.push("two");
list1.push("three");
var list2:Vector.<String> = new Vector.<String>();
list2.push("four");
list2.push("five");
var bridge:ListBridge = new ListBridge(list1, list2);
for (var i:int = 0; i < bridge.length; ++i)
{
	trace(i + ": " + bridge[i]);
}
 
// prints...
0: one
1: two
2: three
3: four
4: five

Or you could go with the higher-performance, type-safe route and not use the index operator at all:

public class ListBridge
{
	private var list1:Vector.<String>;
	private var list2:Vector.<String>;
 
	public function ListBridge(list1:Vector.<String>, list2:Vector.<String>)
	{
		this.list1 = list1;
		this.list2 = list2;
	}
 
	public function getIndex(index:uint): String
	{
		if (index < list1.length)
		{
			return list1[index];
		}
		else
		{
			return list2[index - list1.length];
		}
	}
 
	public function setIndex(index:uint, value:String): void
	{
		if (index < list1.length)
		{
			list1[index] = value;
		}
		else
		{
			list2[index - list1.length] = value;
		}
	}
 
	public function get length(): uint
	{
		return list1.length + list2.length;
	}
}
 
var list1:Vector.<String> = new Vector.<String>();
list1.push("one");
list1.push("two");
list1.push("three");
var list2:Vector.<String> = new Vector.<String>();
list2.push("four");
list2.push("five");
var bridge:ListBridge = new ListBridge(list1, list2);
for (var i:int = 0; i < bridge.length; ++i)
{
	trace(i + ": " + bridge.getIndex(i));
}
 
// prints...
0: one
1: two
2: three
3: four
4: five

With C#, you can have the performance, type safety, and index operator. With AS3, you must choose at least one to give up due to the lack of indexers.

Next, let’s discuss conversion functions. These are special functions that allow you to change the behavior of casts by having your function called instead of the default behavior:

public class Pennies
{
	public int Quantity { get; private set; }
 
	public Pennies(int quantity)
	{
		Quantity = quantity;
	}
 
	public static explicit operator Pennies(Nickels nickels)
	{
		return new Pennies(nickels.Quantity * 5);
	}
 
	public static implicit operator Pennies(Dimes dimes)
	{
		return new Pennies(dimes.Quantity * 10);
	}
}
 
public class Nickels
{
	public int Quantity { get; private set; }
 
	public Nickels(int quantity)
	{
		Quantity = quantity;
	}
}
 
public class Dimes
{
	public int Quantity { get; private set; }
 
	public Dimes(int quantity)
	{
		Quantity = quantity;
	}
}
 
Nickels nickels = new Nickels(3);
Pennies nickelsInPennies = (Pennies)nickels; // cast required
Debug.Log(nickelsInPennies.Quantity + " pennies in " + nickels.Quantity + " nickels");
 
Dimes dimes = new Dimes(2);
Pennies dimesInPennies = dimes; // cast not required
Debug.Log(dimesInPennies.Quantity + " pennies in " + dimes.Quantity + " dimes");

There are two types of conversion functions: explicit and implicit . The only difference is that explicit conversions require you to explicitly cast one variable to another type. For example, you couldn’t normally assign a Nickels to a Pennies . The implicit conversion functions don’t have this requirement. They do allow you to assign a Nickels to a Pennies , similar to how you can assign an integer to a floating-point variable even when their types are unrelated.

AS3, once again, has none of this functionality. If you want to convert between types, you’ll need to make a function or constructor to do so. Since you can have only one constructor in an AS3 class, you’ll probably use a function like so:

public class Pennies
{
	private var _quantity:int;
	public function get quantity(): int { return _quantity; }
 
	public function Pennies(quantity:int)
	{
		_quantity = quantity;
	}
 
	public static function fromNickels(nickels:Nickels): Pennies
	{
		return new Pennies(nickels.quantity * 5);
	}
 
	public static function fromDimes(dimes:Dimes): Pennies
	{
		return new Pennies(dimes.quantity * 10);
	}
}
 
public class Nickels
{
	private var _quantity:int;
	public function get quantity(): int { return _quantity; }
 
	public function Nickels(quantity:int)
	{
		_quantity = quantity;
	}
}
 
public class Dimes
{
	private var _quantity:int;
	public function get quantity(): int { return _quantity; }
 
	public function Dimes(quantity:int)
	{
		_quantity = quantity;
	}
}
 
var nickels:Nickels = new Nickels(3);
var nickelsInPennies:Pennies = Pennies.fromNickels(nickels);
trace(nickelsInPennies.quantity + " pennies in " + nickels.quantity + " nickels");
 
var dimes:Dimes = new Dimes(2);
var dimesInPennies:Pennies = Pennies.fromDimes(dimes);
trace(dimesInPennies.quantity + " pennies in " + dimes.quantity + " dimes");

Lastly for today, let’s discuss both languages’ approaches to functions that take a variable number of arguments/parameters. C#’s approach is to use the params keyword in conjunction with a typed array as the last parameter:

public int Sum(int baseValue, params int[] values)
{
	int sum = baseValue;
	for (int i = 0; i < values.Length; ++i)
	{
		sum += values[i];
	}
	return sum;
}
 
int sum = Sum(10, 1, 2, 3);
Debug.Log(sum); // prints 16

AS3, on the other hand, has two approaches. The first is to use the arguments keyword to implicitly give access to the arguments list to every function:

public function sum(baseValue:int): int
{
	var sum:int = baseValue;
	var numFixedArgs:int = 1;
	var numTotalArgs:int = arguments.length;
	for (var i:int = numFixedArgs; i < numTotalArgs; ++i)
	{
		sum += arguments[i];
	}
	return sum;
}
 
var sum:int = sum(10, 1, 2, 3);
trace(sum); // prints 16

This allows you to index the fixed (non-variable) arguments too, but is quite slow , doesn’t indicate to the caller that more arguments are expected, and requires keeping the numFixedArgs variable in sync with the parameters list. Luckily, AS3 has another approach to the problem with its ...rest syntax:

public function sum(baseValue:int, ...values)
{
	var sum:int = baseValue;
	for (var i:int = 0; i < values.length; ++i)
	{
		sum += values[i];
	}
	return sum;
}
 
var sum:int = Sum(10, 1, 2, 3);
trace(sum); // prints 16

This code is much more similar to the C# version than with the arguments approach. It indicates to the caller that more arguments are expected and segments the fixed arguments from the variable ones. Unfortunately, the variable arguments are not typed so it’s unclear to the caller what is expected and values is a relatively-slow and untyped Array . At least, in the case of this feature, AS3 has some built-in support.

That wraps up today’s topics. As usual in this series, we’ll end with a summary comparing how C# and AS3 handle each of the concepts from this article:

////////
// C# //
////////
 
public class ListBridge
{
	private List<String> list1;
	private List<String> list2;
 
	public ListBridge(List<String> list1, List<String> list2)
	{
		this.list1 = list1;
		this.list2 = list2;
	}
 
	// indexer
	public String this[int index]
	{
		get
		{
			if (index < list1.Count)
			{
				return list1[index];
			}
			else
			{
				return list2[index - list1.Count];
			}
		}
		set
		{
			if (index < list1.Count)
			{
				list1[index] = value;
			}
			else
			{
				list2[index - list1.Count] = value;
			}
		}
	}
 
	// conversion operator
	public static explicit operator List<String>(ListBridge bridge)
	{
		List<String> combined = new List<String>();
		foreach (String str in list1)
		{
			combined.Add(str);
		}
		foreach (String str in list2)
		{
			combined.Add(str);
		}
		return combined;
	}
 
	// variable number of arguments
	public void Add(params String[] values)
	{
		foreach (String str in values)
		{
			list2.Add(str);
		}
	}
}
/////////
// AS3 //
/////////
 
public dynamic class ListBridge extends Proxy
{
	private var list1:Vector.<String>;
	private var list2:Vector.<String>;
 
	public function ListBridge(list1:Vector.<String>, list2:Vector.<String>)
	{
		this.list1 = list1;
		this.list2 = list2;
	}
 
	// indexer - get part
	override flash_proxy function getProperty(index:*): *
	{
		if (index < list1.length)
		{
			return list1[index];
		}
		else
		{
			return list2[index - list1.length];
		}
	}
 
	// indexer - set part
	override flash_proxy function setProperty(index:*, value:*): void
	{
		if (index < list1.length)
		{
			list1[index] = value;
		}
		else
		{
			list2[index - list1.length] = value;
		}
	}
 
 
	// conversion function - operator impossible
	public static function toList(bridge:ListBridge): Vector.<String>
	{
		var combined:Vector.<String> = new Vector.<String>();
		for each (var str:String in list1)
		{
			combined.push(str);
		}
		for each (str in list2)
		{
			combined.push(str);
		}
		return combined;
	}
 
	// variable number of arguments
	public function push(...values): void
	{
		for each (var str:String in values)
		{
			list2.push(str);
		}
	}
}

Next week we’ll (mostly) finish up the special functions subject before moving on to exciting new concepts like structures, enumerations, and generics. Stay tuned!

Spot a bug? Have a question or suggestion? Post acomment !





About List