From AS3 to C#, Part 8: More Special Functions
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”).
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 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 a comment!
#1 by Gary W on September 12th, 2014 ·
Loving these articles! Keep it up man :D