You are on page 1of 42

Object Oriented Programming

Interfaces, Callbacks
Delegates and Events

Contents
Introduction to interfaces
Example An IMeasureable interface
Callback functions
Delegates
Events and event handlers
Summary

Introduction to interfaces

We have already seen how an abstract base class links


related derived class through inheritance

The overridden virtual methods of the abstract base


class must be implemented in the derived classes

There are also occasions when we want unrelated


classes to exhibit similar behaviour

For example we may want to be able to implement


a sorting algorithm on unrelated classes

Introduction to interfaces
For example, we may want to sort objects
of class Square on the basis of the length of
their side
We could easily do this through
polymorphism by implementing an abstract
base class Sortable and using similar
generic programming techniques we looked
at in the last lecture
But, C# (along with Java) doesnt
support multiple inheritance

Introduction to interfaces
Sortable

Shape

Square

Introduction to interfaces

An interface is simply a list of public methods that


any class which implements that interface must
provide implementations of
Unrelated classes can implement a single interface
In our example, we can define a ISortable
interface such that objects of any class which
implements this interface can be sorted on the
basis of some comparative measure
Our Square class can implement the
ISortable interface using the length of side as
the comparative measure

Introduction to interfaces

Our ISortable interface contains a single public


method compareTo() which defines how the
comparison is made
This method is overridden in classes which
implement this interface
public interface ISortable
{
int compareTo(ISortable s);
}

Introduction to interfaces
compareTo(ISortable b) returns 1,0 or 1
depending on whether some chosen instance
field f of a ISortable object is such that :
this.f<b.f
this.f==b.f
this.f>b.f
We can implement this method in class
Square to sort on the basis of length of side

Introduction to interfaces
public class Square : Shape, ISortable
{
private int side;
public Square(int s) { side = s; }
public override void draw() { }
public override double area() { return side * side; }
public int compareTo(ISortable s)
{
Square sq=(Square)s;
if (side<sq.side) return -(1);
if (side > sq.side) return 1;
return 0;
}
}

Introduction to interfaces

We can provide a ShellSort() method which


implements the shell sort algorithm on
arrays on ISortable objects
The compareTo() method of the objects
implementing the interface is called
inside ShellSort()

Introduction to interfaces
public class ArrayAlg
{
public static void shellSort(ISortable[] a)
{
int n = a.Length;
int incr = n / 2;
while (incr >= 1)
{
for (int i = incr; i < n; i++)
{
ISortable temp = a[i];
int j = i;
while (j >= incr && temp.compareTo(a[j - incr]) < 0)
{
a[j] = a[j - incr];
j -= incr;
}
a[j] = temp;
}
incr /= 2;
}
}
}

Introduction to interfaces

To test our program we simply create an


array of Square objects and pass it into the
ShellSort() method
Because of the cast in the compareTo()
method in Square, the correctly
implemented compareTo() method is
called through polymorphism

Introduction to interfaces
public class SortablesTest
{
static void Main(string[] args)
{
Square[] s = new Square[3];
s[0] = new Square(3);
s[0] = new Square(2);
s[0] = new Square(1);
ArrayAlg.shellSort(s);// Sorts on length of side
}
}

An IMeasureable interface

Suppose we have a DataSet class which


computes simple statistics of numbers read
from an input stream
For example, the average and maximum

Input stream

DataSet

average
maximum

public class DataSet


{
public DataSet()
{ sum = 0.0; maximum = 0.0; count = 0; }
public void add(double x)
{
sum += x;
if (count == 0 || maximum < x)
maximum = x;
count++;
}
public double getAverage()
{
if (count == 0) return 0;
else return sum / count;
}
public double getMaximum()
{ return maximum; }
private double sum, maximum;
private int count;
}

An IMeasureable interface

Clearly we would have to modify the DataSet


class if we wanted to get the average of a set of
bank account balances or to find the coin with the
highest value amongst a set
DataSet is not re-useable as it stands
However, if all classes that DataSet objects
operate on implement an IMeasurable interface,
then the class becomes more flexible

An IMeasureable interface
public interface IMeasureable
{
double getMeasure();
}

Thus getMeasure() for BankAccount objects


return the balance and for Coin objects
returns the coin value

An IMeasureable interface
The IMeasurable interface expresses the
commonality amongst objects
The fact that each measurable objects can
return a value relating to its size
DataSet objects can then be used to analyse
collections of objects of any class
implementing this interface with minor
modifications to the code

public class DataSet


{
public DataSet() { sum = 0.0; count = 0; }
public void add(IMeasureable x)
{
sum += x.getMeasure();
if (count == 0 ||
maximum.getMeasure() < x.getMeasure())
maximum = x;
count++;
}
public double getAverage()
{
if (count == 0) return 0;
else return sum / count;
}
public double getMaximum()
{ return maximum.getMeasure(); }
private IMeasureable maximum;
private double sum;
private int count;
}

An IMeasureable interface
class MeasureablesTest
{
static void Main(string[] args)
{
DataSet d=new DataSet();
Coin c1=new Coin(10);
Coin c2=new Coin(20);
d.add(c1);
d.add(c2);
double maxCoin=d.getMaximum();
System.Console.WriteLine("coin max= " + maxCoin);
}
}

Callback functions

The DataSet class is useful as a re-usable class but


is still limited
The IMeasurable interface can only be
implemented by user defined classes
We cant, for example, find the maximum of a
set of Rectangle objects as Rectangle is a predefined class
We can only measure an object in one way. For
example, in the case of BankAccount objects,
we can only measure it in terms of the balance

Callback functions

The solution is to delegate the measuring to a


separate class rather than being the responsibility
of the objects we are measuring
We can create a separate IMeasurer interface
and implement a measure() method in objects
implementing this interface
public interface IMeasurer
{
double measure(Object anObject);
}

Callback functions
public class DataSet
{
public DataSet(IMeasurer m)
{
measurer=m;
}
public void add(Object x)
{
sum=sum+measurer.measure(x);
if (count==0 || maximum<measurer.measure(x))
maximum=measurer.measure(x);
count++;
}
public double getMaximum()
{
return maximum;
}

private
private
private
private

IMeasurer measurer;
double sum;
int count;
double maximum;

Callback functions

A DataSet object makes a callback to the


measure() method of an object implementing the
IMeasurer interface when it needs to measure an
object (such as checking a bank balance)
This is in contrast to calling the getMeasure()
method of an object implementing the
IMeasurable interface
We are now free to design any kind of measures
on an object of any class
For example, we can measure Square objects
by area
We require a SquareMeasurer class which
implements the IMeasurer interface

Callback functions
public class Square : Shape
{
private int side;
public Square(int s) { side = s; }
public override void draw() { }
public override double area() { return side * side; }
}
public class SquareMeasurer : IMeasurer
{
public double measure(Object anObject)
{
Square sq=(Square) anObject;
return sq.area();
}
}

Callback functions
class MeasurersTest
{
static void Main(string[] args)
{
IMeasurer m = new SquareMeasurer();
DataSet data = new DataSet(m);
// Add squares to the data set
data.add(new Square(5));
data.add(new Square (30));
// Get maximum
double max = data.getMaximum();
}
}

Callback functions

The SquareMeasurer object in data carries out the callback


to the measure() method
We have flexibility over our implementation of
SquareMeasurer so that any feature of Square objects can
be measured
Or even defining several measurer classes to measure
different features
Callbacks are used extensively in building graphical user
interfaces
A callback function is added to an event object which is
called when the event is triggered
A managed way of doing this is to encapsulate the
callback function into a delegate object

Delegates

A delegate object holds a reference to a method


with a pre-defined signature
A signature is simply the argument list and
return type of the method
The keyword delegate specifies that we are
defining a delegate object
For example we can define a delegate object
myDelegate which holds a method which
returns void and takes an int and a double as
arguments
public delegate void myDelegate(int arg1, double arg2)

Delegates

A delegate object is initialized with a


(callback) method
The method signature must match the
delegate signature
public delegate void myDelegate(int arg1, double arg2);
public class App
{
public static void Main()
{
myDelegate call = new myDelegate(aMethod);
}
static void aMethod(int k, double x) {}
}

Delegates

Delegate objects can be initialized with several


method calls using the += operator
The method calls can then be invoked in a
chain by passing the correct arguments to the
delegate object
Essentially it amounts to calling methods
through a proxy object and is a powerful
mechanism for event handling as we shall see

Delegates
myDelegate(int,double)
call
aMethod()
anotherMethod()

Invoked by

call(1,2.0)
aMethod(1,2.0)
anotherMethod(1,2.0)

Delegates
public delegate void myDelegate(int arg1, double arg2);
public class App
{
public static void Main()
{
myDelegate call = new myDelegate(aMethod);
call += new myDelegate(anotherMethod);
call(1, 2.0);
}
static void aMethod(int k, double x)
{
System.Console.WriteLine("aMethod " + k + " " + x);
}
static void anotherMethod(int k, double x)
{
System.Console.WriteLine("anotherMethod " + k + " " + x);
}
}

Delegates

Events and event handlers


C# has built in support for event handling
Event handling is usually used in GUIs such as
to notify an application when a mouse click or
button press has occurred
But any type (not just graphical types) can use
events to allow notification of any kind
A type must register its interest in handling a
specific event with an event handler with a predefined signature
This is achieved using a delegate object

Events and event handlers

A type can define an event and an


corresponding event handler which is a
delegate object

public class MyType


{
public delegate void EventHandler(int arg1, int arg2);
public event EventHandler myEvent;
.
.
}

Events and event handlers

An application can then register its interest


in an event by adding an initialized delegate
object to the event using the += operator
public class App
{
public static void Main()
{
MyType m = new MyType();
m.myEvent += new MyType.EventHandler(myHandler);
}
public static void myHandler(int i1, int i2)
{
// Event handler code
}
}

Events and event handlers

Equivalent code is very common in GUI


development where applications register their own
event handlers to respond to events generated by
graphical components such as button clicks
Normally such code is automatically generated
if we are using visual programming techniques
(drag and drop)
However, it is still important to understand
how it all works

Events and event handlers

For example the following code snippet registers a


graphical applications event handler to respond to button
clicks
public class Button
{
public delegate void EventHandler(.....);
public event EventHandler Click;
.
.
}

public class MyGraphicalApp


{
Button button = new Button();
button.Click += new EventHandler(HandleButtonClick);
public void HandleButtonClick(Object sender EventArgs e)
{
// Event handler code
}
}

Events and event handlers

For example, we can generate our own SafeArray


class which fires an event on trying to access it
beyond its bounds
We pass the array index into the event handler
In our simple application, the event handler
simply prints out an error message
In an embedded system application, the array
could be re-allocated to be a larger size in the
event handler

Events and event handlers


public class SafeArray
{
public delegate void OutOfBoundsEventHandler(int arg1);
public event OutOfBoundsEventHandler myOutOfBoundsEvent;
private int[] data;
private int numberOfElements=0;
public SafeArray(int n)
{
numberOfElements = n;
data = new int[numberOfElements];
}
public int access(int elem)
{
if (elem < numberOfElements)
return data[elem];
else
myOutOfBoundsEvent(elem);
return 0;
}
}

// Fire an event

Events and event handlers


public class App
{
public static void Main()
{
SafeArray s = new SafeArray(10);
s.myOutOfBoundsEvent += new
SafeArray.OutOfBoundsEventHandler(myHandler);
s.access(7);
s.access(11); // Out of bounds event generated!
}
public static void myHandler(int i1)
{
System.Console.WriteLine("Index " + i1 + " out of bounds ");
}
}

Summary

We have seen how objects of unrelated classes can exhibit


similar behaviour by implementing an interface
Interfaces support generic programming and avoid multiple
inheritance
We looked at a detailed example involving a IMeasurable
interface
We also introduced callbacks by having a separate measurer
object implemting an IMeasurer interface
A callback function was made to a measure() method which
returned some measure on the object passed to it
We have seen how delegates are objects which encapsulate
method calls
These method calls can be chained using the += operator
We have seen how we can create types which can generate
events and how applications can register their interest in events
by initializing a delegate object with their own event handler

You might also like