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:

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)


Read more in the coming weeks:

Part 6: The Scribble Application

Part 7: Visual Inheritance with Windows Forms

Contact me Contact


Contact me Receive U2U Newsletter.
Looking for a challenging job Download Brochure On Site Training Looking for a challenging job
Favorites Favorites

Copyright © 1999-2010 by U2U