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)
.NET Events Example: The Programmable
Robot (2)
In this chapter, we will extend our Programmable Robot example so
that the robot will raise events while stepping; the Stepping event
before each step, the Stepped event after each step. Furthermore, we
will define event handlers in the control center and bind those to
the robot events.
Defining RobotEventHandler, the Event Delegate class
We define an event reference by means of a delegate class. Now,
to make our example somewhat more exiting, we will define that
delegate class ourselves. This is in most cases not necessary, as
the .NET framework offers us the System.EventHandler delegate.
It is important to notice that the .NET design guidelines define
the signature of an event handler, but as we want to discuss a
general approach first, we do not yet follow those design guidelines
yet. Later in this chapter, we will of course make our code design
guidelines compliant.
Our delegate class will be the RobotEventHandler returning a void
in C# or being a Sub in VB.NET. This event delegate is
not-guidelines compliant as its signature has one parameter of type
Robot.
C#
namespace U2U.Robotica
{
public delegate bool Instruction(Robot robot, int delay);
public delegate void RobotEventHandler(Robot robot);
public class Robot
{...}
...
}
VB.NET
Namespace U2U.Robotica
Public Delegate Function Instruction(ByVal robot As Robot, _
ByVal delay As Integer) As Boolean
Public Delegate Sub RobotEventHandler(ByVal robot As Robot)
Public Class Robot :... End Clas
End Namespace
Defining the robot Events Stepping and Stepped
Event references are typically defined as
public field variables. The property style is not used for events,
except in some rare cases in C#, discussed later on in this chapter.
Thus, in the Robot class we define two event references as public
field variables, the Stepping and Stepped events:
C#
public class Robot
{
private string name;
private Point location;
private Instruction program;
public event RobotEventHandler Stepping;
public event RobotEventHandler Stepped;
public Robot(string name, Point location)
{
this.name = name;
this.location = location;
}
public override String ToString()
{
return "Robot " + name + " at " + location;
}
public string Name
{
get {return name;}
}
public Instruction Program
{
get {return program; }
set {program = value; }
}
public bool StartProgram()
{
return program(this, 10);
}
public void Step(Size size)
{
Stepping(this);
location += size;
Console.WriteLine(ToString());
Stepped(this);
}
}
VB.NET
Public Class Robot
Private nameValue As String
Private location As Point
Private programValue As Instruction
Public Event Stepping As RobotEventHandler
Public Event Stepped As RobotEventHandler
Public Sub New(ByVal name As String, ByVal location As Point)
nameValue = name
Me.location = location
End Sub
Public Overrides Function ToString() As String
Return "Robot " & name & " at " & location.ToString()
End Function
Public ReadOnly Property Name() As String
Get
Return nameValue
End Get
End Property
Public Property Program() As Instruction
Get
Return programValue
End Get
Set(ByVal Value As Instruction)
programValue = Value
End Set
End Property
Public Function StartProgram() As Boolean
Return programValue(Me, 10)
End Function
Public Sub [Step](ByVal size As Size)
RaiseEvent Stepping(Me)
location.Offset(size.Width, size.Height)
Console.WriteLine(ToString())
RaiseEvent Stepped(Me)
End Sub
End Class
Raising the Stepping and Stepped Events
In the code of the Robot class here above, we also adapted the
Step method: the Stepping event is raised in the beginning of the
procedure, the Stepped event is raised at the end of the procedure.
The C# syntax for raising the events is similar to a function call;
the VB.NET syntax makes use of the RaiseEvent keyword.
C#
Stepping(this);
Stepped(this);

VB.NET
RaiseEvent Stepping(Me)
RaiseEvent Stepped(Me)

Defining Event Handlers
The control center defines two event handlers Robot_Stepped and Robot_Stepping and in order to keep our example
simple, they are defined as static (or shared) members. As it is
more common to work with non-static instance method, we will ask you
to adapt these event handlers later in this chapter. Notice that the
signature of both event handlers matches the event delegate RobotEventHandler.
C#
namespace U2U.Robotica
{
public class ControlCenter
{
private static void Robot_Stepping(Robot robot)
{
Trace.WriteLine(robot, "Before step");
}
private static void Robot_Stepped(Robot robot)
{
Trace.WriteLine(robot, "After step");
}
...
}
...
}
VB.NET
Namespace U2U.Robotica
Public Class ControlCenter
Private Shared Sub Robot_Stepped(ByVal robot As Robot)
Trace.WriteLine(robot, "After step")
End Sub
Private Shared Sub Robot_Stepping(ByVal robot As Robot)
Trace.WriteLine(robot, "Before step")
End Sub
End Class
End Namespace
Hooking up the Event Handlers to the robot Events
In the Main
procedure of the control center, we hook up the event handlers to
the public event fields of the robot. We instantiate two
RobotEventHandler delegate objects; the constructor arguments are
the Robot_Stepped and Robot_Stepping procedures.
C#
We use the += operators to add event handlers to
the corresponding event r2d2.Stepped or r2d2.Stepping. This code is
similar to the code we had to write in order to delegate a function
to a delegate reference.
// hooking up to the events
Robot r2d2 = new Robot("r2d2", startpoint );
r2d2.Stepping += new RobotEventHandler( Robot_Stepping);
r2d2.Stepped += new RobotEventHandler(Robot_Stepped);
The only thing to be done is run the program and
observe what trace is left by the robot. For a single step, the
console shows a similar output:
Before step: Robot r2d2 at {X=0, Y=3}
Robot r2d2 at {X=0, Y=2}
After step: Robot r2d2 at {X=0, Y=2}
VB.NET
We use the AddHandler operator to add event handlers to the
corresponding event r2d2.Stepped or r2d2.Stepping. Notice that the
AddHandler syntax is a special kind of assignment: we assign an
event reference to a delegate object. As VB.NET does not support
operator overloading, we cannot write a simple assignment as used
with the delegate reference.
' hooking up to the events
Dim r2d2 As New Robot("r2d2", startpoint )
AddHandler r2d2.Stepping, New RobotEventHandler(AddressOf Robot_Stepping)
AddHandler r2d2.Stepped, New RobotEventHandler(AddressOf Robot_Stepped)
r2d2.Program = program
r2d2.StartProgram()
Hooking up multiple event handlers
The C# as well as the VB.NET syntax reveals
that multiple event handlers can be bound to the same event. Let us
for example hook up the Robot_Stepping event handler twice to the
Stepping event:
C#
Robot r2d2 = new Robot("r2d2", startpoint );
r2d2.Stepping += new RobotEventHandler( Robot_Stepping);
r2d2.Stepping += new RobotEventHandler( Robot_Stepping);
r2d2.Stepped += new RobotEventHandler(Robot_Stepped);
VB.NET
Dim r2d2 As New Robot("r2d2", startpoint )
AddHandler r2d2.Stepping, New RobotEventHandler(AddressOf Robot_Stepping)
AddHandler r2d2.Stepping, New RobotEventHandler(AddressOf Robot_Stepping)
AddHandler r2d2.Stepped, New RobotEventHandler(AddressOf Robot_Stepped)
For a single step, the console shows now twice
the trace of the Stepping event:
Before step: Robot r2d2 at {X=0, Y=3}
Before step: Robot r2d2 at {X=0, Y=3}
Robot r2d2 at {X=0, Y=2}
After step: Robot r2d2 at {X=0, Y=2}
WithEvents and Handles keywords (VB.NET only)
In the above code snippet, the AddHandler statement adds the
event handlers to the event references at runtime; it is a late
binding. However, VB.NET offers us an alternative approach by means
of the Handles keyword, so that a procedure can declare that it will
handle a certain events; then, we have an early binding.
As requirement, we must define the object, to whose event fields
we want to add event handlers, as an instance variable and we must
declare its reference with the WithEvents keyword. In our example,
we move therefore the r2d2 reference outside the control center’s
Main procedure and add the WithEvents keyword. Notice that we
declared r2d2 as a shared reference, but this is not a requirement.
Now we can postfix the procedure with the Handles statement, in
order to hook up the procedure to the event of a certain object. In
our example, we hook up the r2d2_ Stepping procedure to the
r2d2.Stepping event, the r2d2_ Stepped procedure to the r2d2.Steped
event:
Public Class ControlCenter
Private Shared WithEvents r2d2 As New Robot("r2d2", New Point())
Private Shared Sub r2d2_Stepping(ByVal robot As Robot) Handles r2d2.Stepping
Trace.WriteLine(robot, "After step")
End Sub
Private Shared Sub r2d2_Stepped(ByVal robot As Robot) Handles r2d2.Stepped
Trace.WriteLine(robot, "Before step")
End Sub
...
End Class
The advantage of the Handles approach is that its syntax is
simple. At the other hand, the event handler can only be bound at
compile time to the events; the binding is immutable as the Handles
keyword can only be written after de declaration line of a
procedure. Visual Studio.NET offers us rapid event handler
generator. Therefore, we select in the left combo box on top of the
code view the object containing the events, i.e. r2d2.
Automatically, the right combo box shows its events, i.e. the
Stepped and Stepping event. By selecting an event in the combo box,
an event handler is generated in code and hooked up to the selected
event by means of the Handles keyword.

Handling multiple events
The WithEvents/Handles approach supports the
binding of one event handler to several events. Therefore, we have
to write multiple handles expressions after the procedure
declaration. In one single statement here below, we hook up both the
Robot_Stepping event handler to the Stepping events of all three
robots r1d1, r2d2 and r3d3:
Public Class ControlCenter
Private Shared WithEvents r1d1 As New Robot("r1d1", New Point())
Private Shared WithEvents r2d2 As New Robot("r2d2", New Point())
Private Shared WithEvents r3d3 As New Robot("r3d3", New Point())
Private Shared Sub robot_Stepping(ByVal robot As Robot) _
Handles r1d1.Stepping, r2d2.Stepping, r3d3.Stepping
Trace.WriteLine(robot, "After step")
End Sub
...
End Class
The Guidelines Compliant RobotEventHandler
In order to make our RobotEventHandler
compliant with the .NET design guidelines, we have to adapt its
signature so that it accepts two parameters named sender and
e. “The sender parameter represents the object that raised
the event. The sender parameter is always of type object,
even if it is possible to use a more specific type. The state
associated with the event is encapsulated in an instance of an event
class named e.” The type of parameter e must be a class derived
from the System.EventArgs class.
Therefore, our RobotEventHandler must at least
look like:
C#
public delegate void RobotEventHandler(object sender, EventArgs e);
VB.NET
Public Delegate Sub RobotEventHandler(ByVal sender As Object, _
ByVal e As EventArgs)
Here the type of the argument e is the base
class EventArgs; this is sufficient if we do not want to pass event
arguments when raising the events in the Robot.Step method:
public class Robot
{
...
public void Step(Size size)
{
Stepping(this, null);
location += size;
Console.WriteLine(ToString());
Stepped(this, null);
}
}
|
Public Class Robot
...
Public Sub [Step](size As Size)
RaiseEvent Stepping(Me, Nothing)
location.Offset(size.Width, _
size.Height)
Console.WriteLine(ToString())
RaiseEvent Stepped(Me, Nothing)
End Sub
End Class
|
We may not forget to adapt the signature of the
event handlers so that they match the new RobotEventHandler
delegate:
C#
namespace U2U.Robotica
{
public class ControlCenter
{
private static void Robot_Stepping(object sender, EventArgs e)
{
Trace.WriteLine(sender, "Before step");
}
private static void Robot_Stepped(object sender, EventArgs e)
{
Trace.WriteLine(sender, "After step");
}
...
}
...
}
VB.NET
Public Class ControlCenter
Private Shared Sub Robot_Stepping(ByVal sender As Object, _
ByVal e As EventArgs)
Trace.WriteLine(sender, "Before step")
End Sub
Private Shared Sub Robot_Stepped(ByVal sender As Object, _
ByVal e As EventArgs)
Trace.WriteLine(sender, "After step")
End Sub
...
End Class
Adding the RobotEventArgs Event Argument Class
From the moment on, we think of passing arguments to the event
handlers, the .NET design guidelines expect us to define our own
event arguments class derived from the System.EventArgs class. For
our example, we define the RobotEventArgs that contains a read-only
location data member.
C#
public class RobotEventArgs : EventArgs
{
private Point location;
public RobotEventArgs(Point location)
{
this.location = location;
}
public Point Location
{
get {return location;}
}
}
VB.NET
Public Class RobotEventArgs
Inherits EventArgs
Private locationValue As Point
Public Sub New(ByVal location As Point)
locationValue = location
End Sub
Public ReadOnly Property Location() As Point
Get
Return Location
End Get
End Property
End Class
With this argument class, we redefine the event
argument type of the RobotEventHandler delegate:
C#
public delegate void RobotEventHandler(object sender, RobotEventArgs e);
VB.NET
Public Delegate Sub RobotEventHandler(ByVal sender As Object, _
ByVal e As RobotEventArgs)
Furthermore, the Robot.Step method can now pass
RobotEventArgs objects when raising events:
C#
public class Robot
{
...
public void Step(Size size)
{
Stepping(this, new RobotEventArgs(location+size) );
location += size;
Console.WriteLine(ToString());
Stepped(this, new RobotEventArgs(location) );
}
}
VB.NET
Public Class Robot
...
Public Sub [Step](ByVal size As Size)
Dim location As Point = Me.location
location.Offset(size.Width, size.Height)
RaiseEvent Stepping(Me, New RobotEventArgs(location) )
Me.location = location
Console.WriteLine(ToString())
RaiseEvent Stepped(Me, New RobotEventArgs(location) )
End Sub
End Class
Finally, we must upgrade each event handler’s
signatures in order to mach the new
RobotEventHandler
delegate. According to the .NET design guidelines, the sender
argument is of type Object. Consequently, it is necessary to cast
the sender reference to the type Robot; only then, we can access the
Robot members, here for example the Name property.
C#
namespace U2U.Robotica
{
public class ControlCenter
{
private static void Robot_Stepping(object sender, RobotEventArgs e)
{
Trace.Write("Before step: ");
Robot robot = (Robot) sender;
Trace.WriteLine(robot.Name + " stepping to " + e.Location);
}
private static void Robot_Stepped(object sender, RobotEventArgs e)
{
Trace.Write("After step: ");
Robot robot = (Robot) sender;
Trace.WriteLine(robot.Name + " arrived at " + e.Location);
}
...
}
...
}
VB.NET
Public Class ControlCenter
Private Shared Sub Robot_Stepped(ByVal sender As Object, _
ByVal e As RobotEventArgs)
Trace.Write("After step: ")
Dim robot As robot = DirectCast(sender, robot)
Trace.WriteLine(robot.Name & " arrived at " & e.Location.ToString())
End Sub
Private Shared Sub Robot_Stepping(ByVal sender As Object, _
ByVal e As RobotEventArgs)
Trace.Write("Before step: ")
Dim robot As robot = DirectCast(sender, robot)
Trace.WriteLine(robot.Name & " stepping to " & e.Location.ToString())
End Sub
...
End Class
Event Accessors (C# only)
Rarely used and only available to C#
programmers are the so-called event-accessors. With them you
redefine the event storing mechanism of an event, by implementing
the event-accessors yourself. The syntax resembles the property
definition, but in stead of the get and set accessors, the event
accessors uses the add and remove accessors. In the following code
snippet we define a private stepped event field and a public event
accessor.
public class Robot
{
public event RobotEventHandler Stepping; // public event field
private event RobotEventHandler stepped; // private event field
public event RobotEventHandler Stepped // public event accessor
{
add
{
stepped += value;
}
remove
{
stepped -= value;
}
}
...
}
The difference between Delegates and Events
Maybe you asked yourself the question: what is
the true difference between a delegate and an event. At first sight,
the answer is simple:
.NET defines the delegate class, the delegate object and the
delegate reference, but only the event reference. .NET defines
neither the event class, neither the event object. In order to
define the type of an event reference, we have to use a delegate
class, and an event reference can only refer to a delegate object:
| |
delegate |
event |
|
class |
yes |
no |
|
object |
yes |
no |
|
reference |
yes |
yes |
Great, but now the question becomes: how does the event reference
differ from the delegate reference?
At first sight no veritable difference is found except that the
compiler reacts in a different way when dealing with events. In C#
this is easily verifiable: Remove for example the event keyword in
front of the definition of the Stepping field. As result, the
Stepping field becomes a delegate type, and the Stepped field stays
an event type:
public class Robot
{
private string name;
private Point location;
private Instruction program;
public RobotEventHandler Stepping; // delegate type
public event RobotEventHandler Stepped; // event type
...
}
Now build your code. Are you surprised to see no compiler errors?
Even running the application makes no difference. So where are the
differences?
We can experience the difference at the client side: try to let
the control center raise an event on the r2d2 robot object. It is
possible to invoke a delegate field, but it is not possible to
invoke an event field from classes external to the class in which
the event field is defined.
Robot r2d2 = new Robot("r2d2", startpoint );
r2d2.Stepping += new RobotEventHandler(Robot_Stepping); // accessible
r2d2.Stepped += new RobotEventHandler(Robot_Stepped); // accessible
r2d2.Stepping(r2d2, new RobotEventArgs(startpoint)); // OK: delegate reference
r2d2.Stepped(r2d2, new RobotEventArgs(startpoint); // Compiler error: event
The compiler gives the following error:
The event 'U2U.Robotica.Robot.Stepped' can only
appear on the left hand side of += or -= (except when used from
within the type 'U2U.Robotica.Robot')
The explanation of this compiler error is found
in the IL code where the delegate and event references are defined:

As you can notice, the C# public Stepping
delegate field is compiled into an IL public Stepping delegate field
(diamond). But the C# public Stepped event field is compiled into an
IL private Stepped delegate field (diamond) together with two public
methods add_Stepped (square) and remove_Stepped (square). In
addition to this, there is also an extra event entree (downwards
triangle).
If we compare an event member to a property
member, we see parallels. For example in the Robot class, the C#
Name property together with the corresponding C# private name field
is compiled into an IL get_Name (square) and an IL set_Name
(square). In addition to this, there is an extra property entree
(upwards triangle).
C# public delegate field = IL public delegate field
C# public event field = IL private delegate field
+ IL public add method
+ IL public remove method
+ IL public event entree
C# private field = IL private field
+ C# public property = IL public get method
+ IL public set method
+ IL public property entree
It may be useful to study the IL code of the
event into more detail.
.event U2U.Robotica.RobotEventHandler Stepped
{
.addon instance void U2U.Robotica.Robot::add_Stepped(class
U2U.Robotica.RobotEventHandler)
.removeon instance void U2U.Robotica.Robot::remove_Stepped(class
U2U.Robotica.RobotEventHandler)
} // end of event Robot::Stepped
From this IL code, we may conclude that an
event refers to two event accessors, an add and to an remove
accessor.
Finally, let us play around with the event
accessors. We write the following code, where we define a private
delegate field stepped linked to public event accessors Stepped:
public class Robot
{
public event RobotEventHandler Stepping; // public event field
private RobotEventHandler stepped; // private delegate field
public event RobotEventHandler Stepped // public event accessor
{
add
{
stepped += value;
}
remove
{
stepped -= value;
}
}
...
}
Then the IL code shows us that the Stepped
event is added together with the add_ Stepped and remove_ Stepped
methods and the private stepped field:

Adding a Protected Virtual Event Raiser
The .NET design guidelines subscribe that we
should add a protected “virtual event raiser” in each class that
raises events, unless the class is sealed. Now what is a virtual
event raiser? Let us dive into the code of our Robot class and
recall first how for example the stepping event was raised:
C#
Stepping(this, new RobotEventArgs(location+size));
VB.NET
RaiseEvent Stepping(Me, New RobotEventArgs(location))
We should add a special function that raises
those events; this function must be declared protected and virtual
(overridable). Such kinds of functions are called virtual event
raisers, and their function name should have the form OnEventName.
Therefore, we add to our Robot class the
OnStepping and OnStepped event raisers. The purpose of those
functions is to allow a class, derived from our Robot class, to
handle the Stepping event by simply overriding the event raiser.
Handling events in this way is more natural than working with an
event handler.
C#
public void Step(Size size)
{
OnStepping(new RobotEventArgs(location+size));
location += size;
Console.WriteLine(ToString());
OnStepped(new RobotEventArgs(location));
}
protected virtual void OnStepping(RobotEventArgs e)
{
if (Stepping != null)
{
Stepping(this, e);
}
}
protected virtual void OnStepped(RobotEventArgs e)
{
if (Stepped != null)
{
Stepped(this, e);
}
}
VB.NET
Public Sub [Step](ByVal size As Size)
Dim location As Point = Me.location
location.Offset(size.Width, size.Height)
OnStepping(New RobotEventArgs(location))
Me.location = location
Console.WriteLine(ToString())
OnStepped(New RobotEventArgs(location))
End Sub
Protected Overridable Sub OnStepping(ByVal e As RobotEventArgs)
RaiseEvent Stepping(Me, New RobotEventArgs(location))
End Sub
Protected Overridable Sub OnStepped(ByVal e As RobotEventArgs)
RaiseEvent Stepped(Me, New RobotEventArgs(location))
End Sub
The complete code of this example
In the following code list, we introduced three
robots r1d1, r2d2 and r3d3, and two control centers earth and moon.
C#
using System;
using System.Threading;
using System.Drawing;
using System.Diagnostics;
using System.ComponentModel;
using System.Windows.Forms;
namespace U2U.Robotica
{
public delegate bool Instruction(Robot robot, int delay);
public delegate void RobotEventHandler(object sender, RobotEventArgs e);
public class RobotEventArgs : EventArgs
{
private Point location;
public RobotEventArgs(Point location)
{
this.location = location;
}
public Point Location
{
get {return location;}
}
}
public class Robot
{
private string name;
private Point location;
private Instruction program;
public event RobotEventHandler Stepping;
public event RobotEventHandler Stepped;
public Robot(string name, Point location)
{
this.name = name;
this.location = location;
}
public override String ToString()
{
return "Robot " + name + " at " + location;
}
public string Name
{
get {return name;}
}
public Instruction Program
{
get {return program; }
set {program = value; }
}
public bool StartProgram()
{
return program(this, 10);
}
public void Step(Size size)
{
OnStepping(new RobotEventArgs(location+size));
location += size;
Console.WriteLine(ToString());
OnStepped(new RobotEventArgs(location));
}
protected virtual void OnStepping(RobotEventArgs e)
{
if (Stepping != null)
{
Stepping(this, e);
}
}
protected virtual void OnStepped(RobotEventArgs e)
{
if (Stepped != null)
{
Stepped(this, e);
}
}
}
public class ControlCenter
{
private string center;
public ControlCenter(string center)
{
this.center = center;
}
private void Robot_Stepped(object sender, RobotEventArgs e)
{
Trace.Write("After step: ", center);
Robot robot = (Robot) sender;
Trace.WriteLine(robot.Name + " arrived at " + e.Location);
}
private void Robot_Stepping(object sender, RobotEventArgs e)
{
Trace.Write("Before step: ", center);
Robot robot = (Robot) sender;
Trace.WriteLine(robot.Name + " stepping to " + e.Location);
}
static void Main(string[] args)
{
Trace.Listeners.Add(new TextWriterTraceListener(System.Console.Out));
Point startpoint = new Point();
Size stepsize = new Size(1,1);
Robot r1d1 = new Robot("r1d1", startpoint );
Robot r2d2 = new Robot("r2d2", startpoint );
Robot r3d3 = new Robot("r3d3", startpoint );
ControlCenter earth = new ControlCenter("Earth");
ControlCenter moon = new ControlCenter("Moon");
// hooking up to both events
r1d1.Stepped += new RobotEventHandler( moon.Robot_Stepped );
r1d1.Stepping += new RobotEventHandler( moon.Robot_Stepping );
r1d1.Stepped += new RobotEventHandler( earth.Robot_Stepped );
r1d1.Stepping += new RobotEventHandler( earth.Robot_Stepping );
r2d2.Stepped += new RobotEventHandler( moon.Robot_Stepped );
r2d2.Stepping += new RobotEventHandler( moon.Robot_Stepping );
r3d3.Stepped += new RobotEventHandler( earth.Robot_Stepped );
r3d3.Stepping += new RobotEventHandler( earth.Robot_Stepping );
Instruction swing = new Instruction(InstructionLib.Swing);
Instruction zigzag = new Instruction(InstructionLib.ZigZag);
Instruction bump = new Instruction(InstructionLib.Bump);
Instruction[] instructions = new Instruction[] {zigzag, bump, swing};
r1d1.Program = (Instruction) Instruction.Combine(instructions);
instructions = new Instruction[] {zigzag, bump, swing, bump};
r2d2.Program = (Instruction) Instruction.Combine(instructions);
instructions = new Instruction[] {swing, swing, swing};
r3d3.Program = (Instruction) Instruction.Combine(instructions);
r1d1.StartProgram();
r2d2.StartProgram();
r3d3.StartProgram();
}
}
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
Option Strict On
Imports System
Imports System.Drawing
Imports System.Threading
Imports System.Diagnostics
Namespace U2U.Robotica
Public Delegate Function Instruction(ByVal robot As Robot, _
ByVal delay As Integer) As Boolean
Public Delegate Sub RobotEventHandler(ByVal sender As Object, _
ByVal e As RobotEventArgs)
Public Class RobotEventArgs
Inherits EventArgs
Private locationValue As Point
Public Sub New(ByVal location As Point)
locationValue = location
End Sub
Public ReadOnly Property Location() As Point
Get
Return Location
End Get
End Property
End Class
Public Class Robot
Private nameValue As String
Private location As Point
Private programValue As Instruction
Public Event Stepping As RobotEventHandler
Public Event Stepped As RobotEventHandler
Public Sub New(ByVal name As String, ByVal location As Point)
nameValue = name
Me.location = location
End Sub
Public Overrides Function ToString() As String
Return "Robot " & Name & " at " & location.ToString()
End Function
Public ReadOnly Property Name() As String
Get
Return nameValue
End Get
End Property
Public Property Program() As Instruction
Get
Return programValue
End Get
Set(ByVal Value As Instruction)
programValue = Value
End Set
End Property
Public Function StartProgram() As Boolean
Return programValue(Me, 10)
End Function
Public Sub [Step](ByVal size As Size)
Dim location As Point = Me.location
location.Offset(size.Width, size.Height)
OnStepping(New RobotEventArgs(location))
Me.location = location
Console.WriteLine(ToString())
OnStepped(New RobotEventArgs(location))
End Sub
Protected Overridable Sub OnStepping(ByVal e As RobotEventArgs)
RaiseEvent Stepping(Me, New RobotEventArgs(location))
End Sub
Protected Overridable Sub OnStepped(ByVal e As RobotEventArgs)
RaiseEvent Stepped(Me, New RobotEventArgs(location))
End Sub
End Class
Public Class ControlCenter
Private center As String
Public Sub New(ByVal center As String)
Me.center = center
End Sub
Private Sub Robot_Stepped(ByVal sender As Object, _
ByVal e As RobotEventArgs)
Trace.Write("After step: ", center)
Dim robot As robot = DirectCast(sender, robot)
Trace.WriteLine(robot.Name & " arrived at " & e.Location.ToString())
End Sub
Private Sub Robot_Stepping(ByVal sender As Object, _
ByVal e As RobotEventArgs)
Trace.Write("Before step: ", center)
Dim robot As robot = DirectCast(sender, robot)
Trace.WriteLine(robot.Name & " stepping to " & e.Location.ToString())
End Sub
Public Shared Sub Main()
Trace.Listeners.Add(New TextWriterTraceListener(System.Console.Out))
Dim startpoint As Point
Dim stepsize As New Size(1, 1)
Dim r1d1 As New Robot("r1d1", startpoint)
Dim r2d2 As New Robot("r2d2", startpoint)
Dim r3d3 As New Robot("r3d3", startpoint)
Dim earth As New ControlCenter("Earth")
Dim moon As New ControlCenter("Moon")
' hooking up the events
AddHandler r1d1.Stepping, _
New RobotEventHandler(AddressOf moon.Robot_Stepping)
AddHandler r1d1.Stepped, _
New RobotEventHandler(AddressOf moon.Robot_Stepped)
AddHandler r1d1.Stepping, _
New RobotEventHandler(AddressOf earth.Robot_Stepping)
AddHandler r1d1.Stepped, _
New RobotEventHandler(AddressOf earth.Robot_Stepped)
AddHandler r2d2.Stepping, _
New RobotEventHandler(AddressOf moon.Robot_Stepping)
AddHandler r2d2.Stepped, _
New RobotEventHandler(AddressOf moon.Robot_Stepped)
AddHandler r3d3.Stepping, _
New RobotEventHandler(AddressOf earth.Robot_Stepping)
AddHandler r3d3.Stepped, _
New RobotEventHandler(AddressOf earth.Robot_Stepped)
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
Dim instructions As Instruction() = {zigzag, bump, swing}
program = DirectCast(Instruction.Combine(instructions), Instruction)
r1d1.Program = program
instructions = New Instruction() {zigzag, bump, swing, bump}
program = DirectCast(Instruction.Combine(instructions), Instruction)
r2d2.Program = program
instructions = New Instruction() {swing, swing, swing}
program = DirectCast(Instruction.Combine(instructions), Instruction)
r3d3.Program = program
r1d1.StartProgram()
r2d2.StartProgram()
r3d3.StartProgram()
End Sub
End Class
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)
Thread.Sleep(delay)
Next
End Function
End Class
End Namespace
See also:
Read more in the coming weeks:
Part 6: The Scribble Application
Part 7: Visual Inheritance with Windows Forms
|