U2U Article: "Programming with delegates
and events in .NET"
Copyright © 2002 by
U2U nv/sa, Belgium. All rights reserved.
Please address any questions or suggestions to
U2U.
Programming with delegates and events in .NET
Author: Wim UYTTERSPROT,
wim@u2u.be
Part 1: Introducing
Delegates
Part 2: Defining
your own Delegate class
Part 3: Delegates
Example: The Programmable Robot
Part 4: Introducing .NET Events
Part 5: .NET Events
Example: The Programmable Robot(2)
Part 3: Delegates Example: The Programmable Robot
Introduction
It is trivial that a programmable robot is a robot that you can program.
However, what does it mean? It means that the controller of the robot
must be able to send at runtime a set of instructions to the robot object;
the robot must than be able to execute those instructions. The difficulty
hereby is that the set of instructions are unknown to the robot at design
time. But as you may guess yet, the .NET delegates will help us out.
In order to keep it simple, we will begin with a robot class having
limited implementation code. We just allow our robot objects to “step”.
The initial code
At creation, our robot receives a name and a starting position. The
robot’s position cannot be modified directly; it can only change by
sending a Step message to the robot.
public class Robot
{
private string name;
private Point location;
public Robot(string name,
Point location)
{
this.name = name;
this.location = location;
}
public override String ToString()
{
return "Robot " + name
+ " at " + location;
}
public void Step(Size size)
{
location += size;
Console.WriteLine(ToString());
}
}
|
Public Class Robot
Private name As String
Private location As Point
Public Sub New(name As String, _
ByVal location As Point)
Me.name = name
Me.location = location
End Sub
Public Overrides Function ToString()_
As String
Return "Robot " & name & _
" at " & location.ToString()
End Function
Public Sub [Step](ByVal size As Size)
location.Offset(size.Width,
size.Height)
Console.WriteLine(ToString())
End Sub
End Class
|
The Step method represents the basic movement
of the robot. Now let us add a robot control center; for the moment,
it will only tests the good working of the communication with the
robot. Later on, the control center will use the Step method in the
instructions sent to the robots at runtime:
public class ControlCenter
{
public static void Main()
{
Point startpoint = new Point();
Size stepsize = new Size(1,1);
Robot r2d2 = new Robot("r2d2",
startpoint );
for (int i=0; i<=10; i++)
{
r2d2.Step( stepsize );
}
}
}
|
Public Class ControlCenter
Public Shared Sub Main()
Dim startpoint As Point
Dim stepsize As New Size(1, 1)
Dim r2d2 As New Robot("r2d2", _
startpoint)
Dim i As Integer
For i = 0 To 10
r2d2.Step(stepsize)
Next
End Sub
End Class
|
The instruction library
The robot control center will send instruction sets to the
robots. Therefore, we define a special class InstructionLib, a
collection of static or shared procedures containing the
instructions to be sent to the robots. As example, we add three
functions and four properties to it:
C#
public class InstructionLib
{
public static Size StepLeft
{
get { return new Size(-1,0); }
}
public static Size StepRight
{
get { return new Size(+1,0); }
}
public static Size StepForward
{
get { return new Size(0,+1); }
}
public static Size StepBack
{
get { return new Size(0,+1); }
}
public static bool Swing(Robot robot, int delay)
{
Trace.WriteLine("Swinging");
for (int i=0; i<=5; i++)
{
robot.Step(StepLeft);
robot.Step(StepRight);
robot.Step(StepForward);
Thread.Sleep(delay);
}
return true;
}
public static bool ZigZag(Robot robot, int delay)
{
Trace.WriteLine("ZigZagging");
for (int i=0; i<=5; i++)
{
robot.Step(StepLeft);
robot.Step(StepRight);
Thread.Sleep(delay);
}
return true;
}
public static bool Bump(Robot robot, int delay)
{
Trace.WriteLine("Bumping");
for (int i=0; i<=2; i++)
{
robot.Step(StepBack);
Thread.Sleep(delay);
}
return true;
}
}
VB.NET
Public Class InstructionLib
Public Shared ReadOnly Property StepLeft() As Size
Get
Return New Size(-1, 0)
End Get
End Property
Public Shared ReadOnly Property StepRight() As Size
Get
Return New Size(+1, 0)
End Get
End Property
Public Shared ReadOnly Property StepForward() As Size
Get
Return New Size(0, 1)
End Get
End Property
Public Shared ReadOnly Property StepBack() As Size
Get
Return New Size(0, -1)
End Get
End Property
Public Shared Function Swing(ByVal robot As Robot, ByVal delay As Integer) _
As Boolean
Trace.WriteLine("Swinging")
Dim i As Integer
For i = 0 To 5
robot.Step(StepLeft)
robot.Step(StepRight)
robot.Step(StepForward)
Thread.Sleep(delay)
Next
End Function
Public Shared Function ZigZag(ByVal robot As Robot, ByVal delay As Integer) _
As Boolean
Trace.WriteLine("ZigZagging")
Dim i As Integer
For i = 0 To 5
robot.Step(StepLeft)
robot.Step(StepRight)
Thread.Sleep(delay)
Next
End Function
Public Shared Function Bump(ByVal robot As Robot, ByVal delay As Integer) _
As Boolean
Trace.WriteLine("Bumping");
Dim i As Integer
For i = 0 To 2
robot.Step(StepBack)
Next
End Function
End Class
The above code allows us to adapt the Main function of the
control center:
public class ControlCenter
{
public static void Main()
{
Point startpoint = new Point();
Size stepsize = new Size(1,1);
Robot r2d2 = new Robot("r2d2",
startpoint );
InstructionLib.Swing(r2d2, 10);
}
}
|
Public Class ControlCenter
Public Shared Sub Main()
Dim startpoint As Point
Dim stepsize As New Size(1, 1)
Dim r2d2 As New Robot("r2d2",
startpoint)
InstructionLib.Swing(r2d2, 10)
End Sub
End Class
|
Here, the control center calls here the InstructionLib.Swing
function with as argument the robot r2d2 and a delay of 10
milliseconds. Logically, the control center invokes each "Step" on
the robot; the knowledge how to swing is not transferred as a whole
to the robot but stays in the control center.
The Instruction delegate class
What we want to realize now is that the control center can define
instructions in a library and transfer a set of instructions as a
whole to the robot. Only then, we will obtain a programmable robot.
This brings us to the concept of the delegate interpreted as a
program that the control center can send to its robots.
We define our delegate type with a signature identical to the Swing,
ZigZag and similar InstructionLib functions:
//C#
public delegate bool Instruction( Robot robot, int delay );
' VB.NET
Public Delegate Function Instruction(ByVal robot As Robot, _
ByVal delay As Integer) As Boolean
Note that we choose to define our delegate outside the Robot
class or any other class in order to make it accessible everywhere.
However, you can do this differently.
The Instruction delegate reference
We will now adapt the robot’s code so that it becomes a
programmable robot. Therefore, our robot must be able to do two
things: firstly, it must be able to accept a program, i.e. an
Instruction object. We realize this by defining a public instance
property together with an instance variable of type Instruction. In
C# the property is named Program, the variable program;
in VB.NET respectively Program and programValue.
Secondly we define a StartProgram instance method that will
invoke the Instruction delegate object:

C#
public class Robot
{
private string name;
private Point location;
private Instruction program;
public Robot(string name, Point location)
{
this.name = name;
this.location = location;
}
public override String ToString()
{
return "Robot " + name + " at " + location;
}
public Instruction Program
{
get {return program; }
set {program = value; }
}
public void StartProgram()
{
program(this, 10);
}
public void Step(Size size)
{
location += size;
Console.WriteLine(ToString());
}
}
VB.NET
Public Class Robot
Private name As String
Private location As Point
Private programValue As Instruction
Public Sub New(ByVal name As String, ByVal location As Point)
Me.name = name
Me.location = location
End Sub
Public Overrides Function ToString() As String
Return "Robot " & name & " at " & location.ToString()
End Function
Public Property Program() As Instruction
Get
Return programValue
End Get
Set(ByVal Value As Instruction)
programValue = Value
End Set
End Property
Public Sub StartProgram()
programValue(Me, 10)
' alternative invokes
programValue.Invoke(Me, 10)
Program()(Me, 10)
Program.Invoke(Me, 10)
End Sub
Public Sub [Step](ByVal size As Size)
location.Offset(size.Width, size.Height)
Console.WriteLine(ToString())
End Sub
End Class
We remark that the implementation code of the StartProgram method
invokes the delegate field program directly. It is also
possible to invoke the delegate property Program instead.
C#
The code to invoke delegate properties in C# syntax stays simple:
public void StartProgram()
{
Program(this, 10); // using the delegate property
}
VB.NET
In VB.NET it may become a little
bit tricky, as we have to use the Invoke method:
Public Sub StartProgram()
Program.Invoke(Me, 10) ' using the delegate property
End Sub
Alternative ways to invoke the delegate in VB.NET are:
programValue.Invoke(Me, 10)
Program()(Me, 10)
The Instruction delegate objects
We are now ready to program and
our robot. In the ControlCenter.Main function, we first create our
program: therefore we create a delegate object of type Instruction
and refer to it with program. The robot’s program is then
uploaded by using a simple assignment. Finally, we send a
StartProgram request to the robot, in order to ask the robot to
execute the program. This proves that we obtained a programmable
robot:
C#
public class ControlCenter
{
static void Main(string[] args)
{
Trace.Listeners.Add(new WriterTraceListener(System.Console.Out));
Point startpoint = new Point();
Size stepsize = new Size(1,1);
Robot r2d2 = new Robot("r2d2", startpoint );
// (1) create the program
Instruction program = new Instruction(InstructionLib.Swing);
// (2) upload the program
r2d2.Program = program;
// (3) request to start the program
r2d2.StartProgram();
}
}
VB.NET
Public Class ControlCenter
Public Shared Sub Main()
Trace.Listeners.Add(New TextWriterTraceListener(System.Console.Out))
Dim startpoint As Point
Dim stepsize As New Size(1, 1)
Dim r2d2 As New Robot("r2d2", startpoint)
' (1) create the program
Dim program As New Instruction(AddressOf InstructionLib.Swing)
' (2) upload the program
r2d2.Program = program
' (3) request to start the program
r2d2.StartProgram()
End Sub
End Class
Combining the Instruction delegate objects
Let us complicate things now and study the technique to upload
several programs to the robot. We may realize this in different
ways. Here we will combine several instructions first into one
single program that we upload as a whole. But as an exercise, you
may do it differently by creating several programs, and upload the
one by one.
C#
public class ControlCenter
{
static void Main(string[] args)
{
Trace.Listeners.Add(new WriterTraceListener(System.Console.Out));
Point startpoint = new Point();
Size stepsize = new Size(1,1);
Robot r2d2 = new Robot("r2d2", startpoint );
Instruction swing = new Instruction(InstructionLib.Swing);
Instruction zigzag = new ruction(InstructionLib.ZigZag);
Instruction bump = new Instruction(InstructionLib.Bump);
// technique 1
Instruction program = null;
program += zigzag;
program += bump;
program += swing;
program += bump;
r2d2.Program = program;
r2d2.StartProgram();
}
}
VB.NET
Public Class ControlCenter
Public Shared Sub Main()
Trace.Listeners.Add(New TextWriterTraceListener(System.Console.Out))
Dim startpoint As Point
Dim stepsize As New Size(1, 1)
Dim r2d2 As New Robot("r2d2", startpoint)
Dim swing As New Instruction(AddressOf InstructionLib.Swing)
Dim zigzag As New Instruction(AddressOf InstructionLib.ZigZag)
Dim bump As New Instruction(AddressOf InstructionLib.Bump)
Dim program As Instruction
' technique 1
program = zigzag
program = DirectCast(Instruction.Combine(program, bump), Instruction)
program = DirectCast(Instruction.Combine(program, swing), Instruction)
program = DirectCast(Instruction.Combine(program, bump), Instruction)
r2d2.Program = program
r2d2.StartProgram()
End Sub
End Class
Another technique is to combine the instruction objects by means
of the static Combine method:
C#
// technique 2
Instruction[] instructions = new Instruction[] {zigzag, , swing, bump};
program = (Instruction) Instruction.Combine(instructions);
VB.NET
' technique 2
Dim instructions As Instruction() = {zigzag, bump, swing, bump}
program = DirectCast(Instruction.Combine(instructions), Instruction)
Output
The result of our testing code will be something like
Bumping
Robot r2d2 at {X=0,Y=1}
Robot r2d2 at {X=0,Y=2}
Robot r2d2 at {X=0,Y=3}
Swinging
Robot r2d2 at {X=-1,Y=3}
...
ZigZagging
Robot r2d2 at {X=-1,Y=12}
...
Bumping
...
Robot r2d2 at {X=0,Y=15}
See also:
Read more next week:
Part 6: The Scribble Application
Part 7: Visual Inheritance with Windows Forms
|