Object Oriented Programming Interfaces Callbacks Delegates and Events
Object Oriented Programming Interfaces, Callbacks Delegates and Events Dr. Mike Spann m. spann@bham. ac. uk
Contents Introduction to interfaces n Example – An IMeasureable interface n Callback functions n Delegates n Events and event handlers n Summary n
Introduction to interfaces n We have already seen how an abstract base class links related derived class through inheritance u n The overridden virtual methods of the abstract base class must be implemented in the derived classes There also occasions when we want unrelated classes to exhibit similar behaviour u 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 n 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 u But, C# (along with Java) doesn’t support multiple inheritance n
Introduction to interfaces Sortable Shape Square
Introduction to interfaces n n 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 u 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 t Our Square class can implement the ISortable interface using the length of side as the comparative measure
Introduction to interfaces n Our ISortable interface contains a single public method compare. To() which defines how the comparison is made u This method is overridden in classes which implement this interface public interface ISortable { int compare. To(ISortable s); }
Introduction to interfaces compare. To(ISortable b) returns – 1, 0 or 1 depending on whether some chosen instance field f of a ISortable object is such that : u this. f<b. f u this. f==b. f u this. f>b. f n We can implement this method in class Square to sort on the basis of length of side n
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 compare. To(ISortable s) { Square sq=(Square)s; if (side<sq. side) return -(1); if (side > sq. side) return 1; return 0; } }
Introduction to interfaces n We can provide a Shell. Sort() method which implements the shell sort algorithm on arrays on ISortable objects u The compare. To() method of the objects implementing the interface is called inside Shell. Sort()
Introduction to interfaces public class Array. Alg { public static void shell. Sort(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. compare. To(a[j - incr]) < 0) { a[j] = a[j - incr]; j -= incr; } a[j] = temp; } incr /= 2; } } }
Introduction to interfaces n To test our program we simply create an array of Square objects and pass it into the Shell. Sort() method u Because of the cast in the compare. To() method in Square, the correctly implemented compare. To() method is called through polymorphism
Introduction to interfaces public class Sortables. Test { 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); Array. Alg. shell. Sort(s); // Sorts on length of side } }
An IMeasureable interface n Suppose we have a Data. Set class which computes simple statistics of numbers read from an input stream u For example, the average and maximum Input stream Data. Set average maximum
public class Data. Set { public Data. Set() { 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 get. Average() { if (count == 0) return 0; else return sum / count; } public double get. Maximum() { return maximum; } private double sum, maximum; private int count; }
An IMeasureable interface n Clearly we would have to modify the Data. Set 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 u Data. Set is not re-useable as it stands u However, if all classes that Data. Set objects operate on implement an IMeasurable interface, then the class becomes more flexible
An IMeasureable interface public interface IMeasureable { double get. Measure(); } n Thus get. Measure() for Bank. Account objects return the balance and for Coin objects returns the coin value
An IMeasureable interface public class Bank. Account: IMeasureable { private double balance; private int account. Number; . . double get. Measure() { return balance; } } public class Coin: IMeasurable { private double value; . . double get. Measure() { return value; } }
An IMeasureable interface The IMeasurable interface expresses the commonality amongst objects u The fact that each measurable objects can return a value relating to its size n Data. Set objects can then be used to analyse collections of objects of any class implementing this interface with minor modifications to the code n
public class Data. Set { public Data. Set() { sum = 0. 0; count = 0; } public void add(IMeasureable x) { sum += x. get. Measure(); if (count == 0 || maximum. get. Measure() < x. get. Measure()) maximum = x; count++; } public double get. Average() { if (count == 0) return 0; else return sum / count; } public double get. Maximum() { return maximum. get. Measure(); } private IMeasureable maximum; private double sum; private int count; }
An IMeasureable interface class Measureables. Test { static void Main(string[] args) { Data. Set d=new Data. Set(); Coin c 1=new Coin(10); Coin c 2=new Coin(20); d. add(c 1); d. add(c 2); double max. Coin=d. get. Maximum(); System. Console. Write. Line("coin max= " + max. Coin); } }
Callback functions n The Data. Set class is useful as a re-usable class but is still limited u The IMeasurable interface can only be implemented by user defined classes u We can’t, for example, find the maximum of a set of Rectangle objects as Rectangle is a predefined class u We can only measure an object in one way. For example, in the case of Bank. Account objects, we can only measure it in terms of the balance
Callback functions n The solution is to delegate the measuring to a separate class rather than being the responsibility of the objects we are measuring u We can create a separate IMeasurer interface and implement a measure() method in objects implementing this interface public interface IMeasurer { double measure(Object an. Object); }
Callback functions public class Data. Set { public Data. Set(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 get. Maximum() { return maximum; } private } IMeasurer measurer; double sum; int count; double maximum;
Callback functions n n A Data. Set 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) u This is in contrast to calling the get. Measure() method of an object implementing the IMeasurable interface We are now free to design any kind of measures on an object of any class u For example, we can measure Square objects by area u We require a Square. Measurer 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 Square. Measurer : IMeasurer { public double measure(Object an. Object) { Square sq=(Square) an. Object; return sq. area(); } }
Callback functions class Measurers. Test { static void Main(string[] args) { IMeasurer m = new Square. Measurer(); Data. Set data = new Data. Set(m); // Add squares to the data set data. add(new Square(5)); // Callback to m. measure() data. add(new Square (30)); // Callback to m. measure() // Get maximum double max = data. get. Maximum(); } }
Callback functions n n n Adding square objects to data enforces callbacks to the measure() method of the Square. Measurer object to be made We have flexibility over our implementation of Square. Measurer so that any feature of Square objects can be measured u Or even defining several measurer classes to measure different features Callbacks are used extensively in building graphical user interfaces u A callback function is added to an event object which is called when the event is triggered u A managed way of doing this is to encapsulate the callback function into a delegate object
Delegates n n A delegate object holds a reference to a method with a pre-defined signature u A signature is simply the argument list and return type of the method The keyword delegate specifies that we are defining a delegate object u For example we can define a delegate object my. Delegate which holds a method which returns void and takes an int and a double as arguments public delegate void my. Delegate(int arg 1, double arg 2)
Delegates n A delegate object is initialized with a (callback) method u The method signature must match the delegate signature public delegate void my. Delegate(int arg 1, double arg 2); public class App { public static void Main() { my. Delegate call = new my. Delegate(a. Method); // We can now treat call as a simple object and pass // it around as such } static void a. Method(int k, double x) {} }
Delegates n Delegate objects can be initialized with several method calls using the += operator u The method calls can then be invoked in a chain by passing the correct arguments to the delegate object u Essentially it amounts to calling methods through a proxy object and is a powerful mechanism for event handling as we shall see t Passing method calls into objects is then equivalent to passing delegate objects
Delegates my. Delegate(int, double) call a. Method() another. Method() Invoked by call(1, 2. 0) a. Method(1, 2. 0) another. Method(1, 2. 0)
Delegates public delegate void my. Delegate(int arg 1, double arg 2); public class App { public static void Main() { my. Delegate call = new my. Delegate(a. Method); call += new my. Delegate(another. Method); call(1, 2. 0); } static void a. Method(int k, double x) { System. Console. Write. Line("a. Method " + k + " " + x); } static void another. Method(int k, double x) { System. Console. Write. Line("another. Method " + k + " " + x); } }
Delegates
Events and event handlers C# has built in support for event handling n Event handling is usually used in GUI’s such as to notify an application when a mouse click or button press has occurred n But any type (not just graphical types) can use events to allow notification of any kind n A type must register its interest in handling a specific event with an event handler with a pre-defined signature u This is achieved using a delegate object n
Events and event handlers n A type can define an event and a corresponding event handler which is a delegate object public class My. Type { public delegate void Event. Handler(int arg 1, int arg 2); public event Event. Handler my. Event; // defines my. Event // and corresponding // event handler. }
Events and event handlers n 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() { My. Type m = new My. Type(); m. my. Event += new My. Type. Event. Handler(my. Handler); } public static void my. Handler(int i 1, int i 2) { // Event handler code } }
Events and event handlers n 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 u Normally such code is automatically generated if we are using visual programming techniques (‘drag and drop’) t However, it is still important to understand how it all works
Events and event handlers n For example the following code snippet registers a graphical applications event handler to respond to button clicks public class Button { public delegate void Event. Handler(. . . ); public event Event. Handler Click; . . } public class My. Graphical. App { Button button = new Button(); button. Click += new Event. Handler(Handle. Button. Click); public void Handle. Button. Click(Object sender Event. Args e) { // Event handler code } }
Events and event handlers n n For example, we can generate our own Safe. Array class which fires an event on trying to access it beyond its bounds We pass the array index into the event handler u In our simple application, the event handler simply prints out an error message u 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 Safe. Array { public delegate void Out. Of. Bounds. Event. Handler(int arg 1); public event Out. Of. Bounds. Event. Handler my. Out. Of. Bounds. Event; private int[] data; private int number. Of. Elements=0; public Safe. Array(int n) { number. Of. Elements = n; data = new int[number. Of. Elements]; } public int access(int elem) { if (elem < number. Of. Elements) return data[elem]; else my. Out. Of. Bounds. Event(elem); return 0; } } // Fire an event
Events and event handlers public class App { public static void Main() { Safe. Array s = new Safe. Array(10); s. my. Out. Of. Bounds. Event += new Safe. Array. Out. Of. Bounds. Event. Handler(my. Handler); s. access(7); s. access(11); // Out of bounds event generated! } public static void my. Handler(int i 1) { System. Console. Write. Line("Index " + i 1 + " out of bounds "); } }
Summary n n n 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 u We also introduced callbacks by having a separate measurer object implementing an IMeasurer interface u 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 u 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
- Slides: 43