From AS3 to C#, Part 9: Even More Special Functions

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

Last week’s articlecontinued the discussion of special types of functions in C#’s class system, including variable numbers of arguments (“var args”), indexers, and conversion operators. Today’s article should finish up the topic of special functions. Read on to learn about the built-in support for delegates, events, and object initializers!

Before we get to events we need to cover delegates. A delegate is a definition of a function’s signature, but not actually the definition a particular function. It’s like the relationship between an interface and the class that implements it. You need an actual function to “implement” the delegate. Here’s how that looks:

public class FunctionalUtilities
{
	// Declare the type of function that Every() will take as a parameter.
	// It will be called to check each element of the array.
	public delegate bool EveryCallback(int a);
 
	// The second parameter can be any function that fits the signature
	// specified by EveryCallback. It must take one int and return a bool.
	public static bool Every(int[] integers, EveryCallback callback)
	{
		for (int i = 0; i < integers.Length; ++i)
		{
			// Call the delegate like any other function
			if (callback(integers[i]) == false)
			{
				return false;
			}
		}
 
		return true;
	}
}
 
public class TestEvery
{
	// This function satisfies the delegate by taking one int and
	// returning a bool
	public bool IsEven(int val)
	{
		return (val % 2) == 0;
	}
 
	public void Foo()
	{
		int[] integers = { 2, 4, 6 };
 
		// Pass the IsEven function as the second parameter. The Every()
		// function will call it.
		bool allEven = FunctionalUtilities.Every(integers, IsEven);
 
		Debug.Log("all integers are even? " + allEven);
	}
}

Delegates essentially give us a way to strongly-type AS3′s Function . With Function , you can pass any function at all or even null . It gives no hint to the caller as to what the expected parameters or return type are. Instead, a runtime error will occur if there is any mismatch. Here’s how the equivalent AS3 would look:

public class FunctionalUtilities
{
	// The second parameter can be any function or even null
	public static function every(integers:Vector.<int>, callback:Function): Boolean
	{
		for (var i:int = 0; i < integers.length; ++i)
		{
			// Call the callback function like any other function
			if (callback(integers[i]) == false)
			{
				return false;
			}
		}
 
		return true;
	}
}
 
public class TestEvery
{
	public function isEven(val:int): Boolean
	{
		return (val % 2) == 0;
	}
 
	public function foo(): void
	{
		var integers:Vector.<int> = new <int>[ 2, 4, 6 ];
 
		// Pass the IsEven function as the second parameter. The Every()
		// function will call it.
		var allEven:Boolean = FunctionalUtilities.every(integers, isEven);
 
		trace("all integers are even? " + allEven);
	}
}

These strongly-typed delegate functions form the basis of C#’s built-in event system. We simply use the event keyword to declare a new event like so:

public class PushButton
{
	// First, declare a delegate specifying the signature of
	// functions the event calls
	public delegate void ClickHandler(PushButton button);
 
	// Next, declare an event with the delegate type
	public event ClickHandler OnClick;
 
	public void Click()
	{
		// Check if anyone has subscribed to the event
		if (OnClick != null)
		{
			// Call the event like a function to call all
			// of the listeners
			OnClick(this);
		}
	}
}
 
public class MyUI
{
	private PushButton okButton;
 
	public MyUI()
	{
		okButton = new PushButton();
 
		// Use the += operator to add a listener to the event
		okButton.OnClick += HandleOKButtonClicked;
 
		// Clicking the button calls the listener
		okButton.Click();
	}
 
	// Declare a function with the type specified by the delegate.
	// This will allow it to be used as an event listener.
	private void HandleOKButtonClicked(PushButton button)
	{
		Debug.Log("OK button clicked");
 
		// Use the -= operator to remove a listener from the event
		okButton.OnClick -= HandleOKButtonClicked;
	}
}

The way we declared the event is akin to the shortcut syntax we used with properties to automatically generate the “backing variable”, getter, and setter:

// Shortcut version
public class Person
{
	public String Name { get; set; }
}
 
// Long version
public class Person
{
	// "backing variable"
	private String name;
 
	public String Name
	{
		// custom getter
		get { return name; }
 
		// custom setter
		set { name = value; }
	}
}

With events, it’s generating code like this for us:

// Shortcut version
public class PushButton
{
	public delegate void ClickHandler(PushButton button);
 
	public event ClickHandler OnClick;
}
 
// Long version
public class PushButton
{
	public delegate void ClickHandler(PushButton button);
 
	// "backing delegate"
	private ClickHandler onClick;
 
	public event ClickHandler OnClick
	{
		// custom "adder"
		add { onClick += value; }
 
		// custom "remover"
		remove { onClick -= value; }
	}
}

This means we can create custom “adder” and “remover” methods. You usually won’t do this, but it does allow you a convenient place to add in addition code. For example, you might optimize by only performing expensive operations when there is at least one listener. Otherwise, no one cares and the operation might be skipped:

public class Enemy
{
	public delegate void MoveHandler(PushButton button);
 
	// "backing delegate"
	private MoveHandler onMove;
 
	private int numMoveListeners;
 
	public event MoveHandler OnMove
	{
		// custom "adder"
		add { onMove += value; numMoveListeners++; }
 
		// custom "remover"
		remove { onMove -= value; numMoveListeners--; }
	}
 
	public void Update()
	{
		if (numMoveListeners > 0)
		{
			// ... perform expensive physics calculations
		}
	}
}

In AS3 we are given the flash.events.Event class to derive from to define the contents of our event instead of callback parameters. We are also given the flash.events.EventDispatcher class to derive from if we want to dispatch events:

// First, extend Event to create the event type
public class ClickEvent extends Event
{
	private _button:PushButton;
 
	public function get button(): PushButton
	{
		return _button;
	}
 
	// Create a constructor to forward the necessary parameters and take any additions
	public ClickEvent(
		type:String,
		button:PushButton,
		bubbles:Boolean = false,
		cancelable:Boolean = false
	)
	{
		super(type, bubbles, cancelable);
		_button = button;
	}
 
	// Override clone() to create copies
	public override function clone(): Event
	{
		return new ClickEvent(type, _button, bubbles, cancelable);
	}
}
 
// Now extend EventDispatcher
public class PushButton extends EventDispatcher
{
	// Declare an event type String
	public static const CLICK:String = "click";
 
	public function click(): void
	{
		// Create an Event object with the event type string
		var clickEvent:ClickEvent = new ClickEvent(CLICK, this);
 
		// Call dispatchEvent to call all the listeners
		dispatchEvent(clickEvent);
	}
}
 
public class MyUI
{
	private var okButton:PushButton;
 
	public function MyUI()
	{
		okButton = new PushButton();
 
		// Use the addEventListener function with the event type string
		// to add a listener to the event
		okButton.addEventListener(PushButton.CLICK, handleOKButtonClicked);
 
		// Clicking the button calls the listener
		okButton.click();
	}
 
	// Declare a function with any signature. If it doesn't take only one
	// parameter-- an Event-- an Error will be thrown at runtime when the
	// event is dispatched.
	private function handleOKButtonClicked(event:ClickEvent): void
	{
		trace("OK button clicked");
 
		// Use the removeEventListener function with the event type string
		// to remove a listener from the event
		okButton.removeEventListener(PushButton.CLIK, handleOKButtonClicked);
	}
}

This system requires quite a bit more code, doesn’t enforce type safety on the listener function, uses an arbitrary event type string rather than variables, requires you to derive from EventDispatcher , and is horrifyinglyslow. It’s the reason programmers like me have created alternatives likeTurboSignals, but they have their own problems that are off-topic for this article.

Lastly for today, let’s discuss a simpler topic: object initializers. It’s common to set several variables on a newly-instantiated class, so C# has a shortcut available:

public class Vector3
{
	public double x;
	public double y;
	public double z;
}
 
// Long version
Vector3 vec = new Vector3();
vec.x = 1;
vec.y = 2;
vec.z = 3;
 
// Shortcut version using object initializer
Vector3 vec = new Vector3() { x = 1, y = 2, z = 3 };
 
// When calling the default constructor and using an object initializer,
// the parentheses are optional too
Vector3 vec = new Vector3 { x = 1, y = 2, z = 3 };

This is like a strongly-typed version of AS3′s with blocks that can only be used at object initialization time. Here’s how it’d look in AS3:

public class Vector3
{
	public var x:Number;
	public var y:Number;
	public var z:Number;
}
 
// Long version
var vec:Vector3 = new Vector3();
vec.x = 1;
vec.y = 2;
vec.z = 3;
 
// Shortcut version using 'with' block
var vec:Vector3 = new Vector3();
with (vec) { x = 1, y = 2, z = 3 };

Unfortunately, with blocks in AS3 are painfully slow as they entailfar morethan just syntax sugar, as in C#.

Let’s wrap up for today with a quick comparison summarizing this article’s topics: delegates, events, and object initializers:

////////
// C# //
////////
 
public class IntVector2
{
	public int x;
	public int y;
}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
public class PushButton
{
	// Delegate
	public delegate void ClickHandler(PushButton button, IntVector2 location);
 
	// Event
	public event ClickHandler OnClick;
 
	public void Click(int x, int y)
	{
		// Object initializer
		IntVector2 location = new IntVector2 { x = x, y = y };
 
 
		// Dispatch event
		OnClick(this, location);
	}
}
/////////
// AS3 //
/////////
 
public class IntVector2
{
	public var x:int;
	public var y:int;
}
 
// Event - parameters require extending Event class
public class ClickEvent extends Event
{
	private _button:PushButton;
	private _location:IntVector2;
 
	public function get button(): PushButton
	{
		return _button;
	}
 
	public function get location(): IntVector2
	{
		return _location;
	}
 
	public ClickEvent(
		type:String,
		button:PushButton,
		location:IntVector2,
		bubbles:Boolean = false,
		cancelable:Boolean = false
	)
	{
		super(type, bubbles, cancelable);
		_button = button;
		_location = location;
	}
 
	public override function clone(): Event
	{
		return new ClickEvent(type, _button, _location, bubbles, cancelable);
	}
}
 
// Event - dispatching requires extending EventDispatcher class
public class PushButton extends EventDispatcher
{
	// Delegate
	// {impossible, use type-unsafe Function instead}
 
	public static const CLICK:String = "click";
 
	public function click(x:int, y:int): void
	{
		// Object initializer - requires with block
		var location:IntVector2 = new IntVector2();
		with (location) { x = x, y = y };
 
		// Dispatch event
		var clickEvent:ClickEvent = new ClickEvent(CLICK, this, location);
		dispatchEvent(clickEvent);
	}
}

Next week we’ll start covering exciting new concepts like structures, enumerations, and generics. Stay tuned!

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





About List