Delegates allows you to hold a function pointer in a variable, and use them at a later time.
The following three classes will work together to demonstrate creating, defining, and calling delegates.
class DelegateHolder
{
var Name : String = "Unnamed";
var Greeting : delegate() = null;
constructor(name: String)
{
this.Name = name;
}
function Introduce()
{
Console.WriteLine("Hi I'm `this.Name`");
}
}
The DelegateHolder
class contains a delegate member variable Greeting
that is set in the constructor to the member function Introduce()
.
class MyMathLib
{
[Static]
function Exponent(baseNum : Integer, exponent : Integer) : Integer
{
var num = 1; // Start at baseNum ^ 0
while(exponent > 0)
{
num = num * baseNum;
--exponent;
}
return baseNum;
}
}
The MyMathLib
class contains just a single static function that returns the product of exponentiation. This function will be used to demonstrate how to use a delegate in the next and final class.
class DelegateDriver : NadaComponent
{
function Initialize(init : CogInitializer)
{
this.DelegateSamples();
}
function DelegateSamples()
{
var gh = new DelegateHolder("gh");
var ef = new DelegateHolder("ef");
// Making a delegate:
var getName : delegate() = gh.Greeting;
// Using a delegate:
getName(); // Hi I'm gh
// Assigning a member function to a delegate:
getName = ef.Greeting;
// Passing a delegate into a function:
DelegateDriver.DelegateCaller(getName); // Hi I'm ef
getName(); // Hi I'm ef
// Assigning a static function to a delegate:
getName = DelegateDriver.DelegateTest;
getName(); // 256
DelegateDriver.DelegateCaller(getName); // 256
// Making a delegate:
var doMath : delegate(a: Integer, b: Integer): Integer = DelegateDriver.Add;
Console.WriteLine(DelegateDriver.BinaryIntOpsCaller(doMath)); // 8
Console.WriteLine(doMath(2,6)); // 8
doMath = DelegateDriver.Multiply;
Console.WriteLine(DelegateDriver.BinaryIntOpsCaller(doMath)); // 15
Console.WriteLine(doMath(2,6)); // 12
doMath = DelegateDriver.Subtract;
Console.WriteLine(DelegateDriver.BinaryIntOpsCaller(doMath)); // 2
Console.WriteLine(doMath(2,6)); // -4
}
[Static]
function Add(a: Integer, b: Integer): Integer
{
return a+b;
}
[Static]
function Multiply(a: Integer, b: Integer): Integer
{
return a*b;
}
[Static]
function Subtract(a: Integer, b: Integer): Integer
{
return a-b;
}
[Static]
function BinaryIntOpsCaller(a: delegate(a: Integer, b: Integer):Integer):Integer
{
return a(5, 3);
}
[Static]
function DelegateCaller(a: delegate())
{
a();
}
[Static]
function DelegateTest()
{
var doMath : delegate(lhs : Integer, rhs : Integer): Integer = MyMathLib.Exponent;
Console.WriteLine(doMath(2, 8));
}
}
The DelegateDriver
class covers a number of different ways to define and call a delegate. Lines 10 and 11 create two DelegateHolder
objects, gh
and ef
, while line 14 creates a delegate variable that's set to the delegate variable found in object gh
:
var gh = new DelegateHolder("gh");
var ef = new DelegateHolder("ef");
var getName : delegate() = gh.Greeting;
Line 17 demonstrates directly calling a delegate using a delegate variable, while line 20 reassigns that delegate to the delegate variable found in object ef
:
getName();
getName = ef.Greeting;
Line 24 passes the delegate variable into a function, DelegateCaller
, which is defined further down the script�that calls the delegate function of any delegate passed into it:
getName = DelegateDriver.DelegateTest;
Line 27 assigns a static function to the delegate variable, DelegateTest
, which defines its own delegate function assigned the to Exponent
function in MyMathLib
:
getName = DelegateDriver.DelegateTest;
Line 30 then passes in the re-assigned variable back into the DelegateCaller
function, showing that the Exponent
function is still called:
DelegateDriver.DelegateCaller(getName);
Line 33 creates a new delegate variable assigning it to another static function, while lines 35 - 44 demonstrates passing a delegate (that is redefined a couple of times) into a function that expects a delegate as a parameter:
var doMath : delegate(a: Integer, b: Integer): Integer = DelegateDriver.Add;
Console.WriteLine(DelegateDriver.BinaryIntOpsCaller(doMath)); // 8
Console.WriteLine(doMath(2,6)); // 8
doMath = DelegateDriver.Multiply;
Console.WriteLine(DelegateDriver.BinaryIntOpsCaller(doMath)); // 15
Console.WriteLine(doMath(2,6)); // 12
doMath = DelegateDriver.Subtract;
Console.WriteLine(DelegateDriver.BinaryIntOpsCaller(doMath)); // 2
Console.WriteLine(doMath(2,6));
Attaching DelegateDriver
to an object and running the project results in the following print statements in the Console Window
:
---------------- Begin Game ---------------
Hi I'm gh
Hi I'm ef
Hi I'm ef
256
256
8
8
15
12
2
-4
Named Parameters
IMPORTANT: At this time the names chosen for the parameters are part of the signature. In order for two function signatures to be the same the names chosen for the parameters must match:
class MyMathLib
{
[Static] // Note named parameters are lhs and rhs:
function Exponent(lhs: Integer, rhs: Integer): Integer
{
return lhs^rhs;
}
}
Consider these tests:
class Driver
{
function DelegateTest()
{
// Here the delegate has named parameters a and b:
var doMath : delegate(a: Integer, b: Integer): Integer = MyMathLib.Exponent;// This won't compile.
}
}
The value being assigned to 'doMath' must be of type 'delegate (a : Integer, b : Integer) : Integer'.
Its type is 'delegate (lhs : Integer, rhs : Integer) : Integer'.
class Driver
{
function DelegateTest()
{
// When declaring a delegate you cannot exclude the parameter names:
var doMath : delegate(Integer, Integer): Integer = MyMathLib.Exponent;// This won't compile.
}
}
Function declaration 'delegate' has an invalid argument list. We found 'UpperIdentifier' but
we expected to find ')'.
class Driver
{
function DelegateTest()
{
// Here, after making the parameter names match, does it work:
var doMath : delegate(lhs: Integer, rhs: Integer): Integer = MyMathLib.Exponent;
Console.WriteLine(doMath(2,8));
}
}
---------------- Begin Game ---------------
256
Member Function Delegates
IMPORTANT: Delegates that point at member functions can create memory leaks if they make cycles. See memory_management for more.
IMPORTANT: Delegates pointing to member functions will throw runtime errors if the instance it was bound with was destroyed:
// Here are three Objects with the ability to hold delegates:
var ab = new DelegateHolder("ab");
var b = new DelegateHolder("b");
ab.Greeting = b.Introduce;
delete b;
ab.Greeting(); // This throws a runtime error
Attempted to access a member of a null handle: Attempted to call a member function on a null object
Null Delegates
IMPORTANT: It is an illegal operation to call a null delegate, so be sure to initialize your delegates before calling!
// Here are three Objects with the ability to hold delegates:
var ab: delegate(a: Integer, b: Integer): Integer = null;
ab(3, 6);// This throws a runtime error
Attempted to invoke a null delegate