Recipe3.18.Adding a Notification Callback Using an Interface


Recipe 3.18. Adding a Notification Callback Using an Interface

Problem

You need a flexible, well-performing callback mechanism that does not make use of a delegate because you need more than one callback method. So the relationship between the caller and the callee is more complex than can easily be represented through the one method signature that you get with a delegate.

Solution

Use an interface to provide callback methods. The INotificationCallbacks interface contains two methods that will be used by a client as callback methods. The first method, FinishedProcessingSubGroup, is called when an amount specified in the amount parameter is reached. The second method, FinishedProcessingGroup, is called when all processing is complete:

 public interface INotificationCallbacks {     void FinishedProcessingSubGroup(int amount);     void FinishedProcessingGroup( ); } 

The NotifyClient class shown in Example 3-11 implements the INotificationCallbacks interface. This class contains the implementation details for each of the callback methods.

Example 3-11. Implementing the INotificationCallbacks interface

 public class NotifyClient : INotificationCallbacks {     public void FinishedProcessingSubGroup(int amount)     {         Console.WriteLine("Finished processing " + amount + " items");     }     public void FinishedProcessingGroup( )     {         Console.WriteLine("Processing complete");     } } 

The Task class shown in Example 3-12 implements its callbacks through the NotifyClient object (see Example 3-11). The Task class contains a field called notificationObj, which stores a reference to the NotifyClient object that is passed to it either through construction or through the AttachToCallback method. The UnAttachCallback method removes the NotifyClient reference from this object. The ProcessSomething method invokes the callback methods.

Example 3-12. Implementing callbacks with the NotifyClient object

 public class Task {     public Task(NotifyClient notifyClient)     {         notificationObj = notifyClient;     }     NotifyClient notificationObj = null;          public void AttachToCallback(NotifyClient notifyClient)    {         notificationObj = notifyClient;    }    public void UnAttachCallback( )    {        notificationObj = null;    }    public void ProcessSomething( )    {        // This method could be any type of processing.        for (int counter = 0; counter < 100; counter++)        {            if ((counter % 10) == 0)            {                if (notificationObj != null)                {                    notificationObj.FinishedProcessingSubGroup(counter);                }            }        }        if (notificationObj != null)        {            notificationObj.FinishedProcessingGroup( );        }    } } 

The CallBackThroughIFace method uses callback features of the Task class as follows:

 public void CallBackThroughIFace( ) {     NotifyClient notificationObj = new NotifyClient( );     Task t = new Task(notificationObj);     t.ProcessSomething( );     Console.WriteLine( );     t.UnAttachCallback( );     t.ProcessSomething( );     Console.WriteLine( );     t.AttachToCallback(notificationObj);     t.ProcessSomething( );     Console.WriteLine( );     t.UnAttachCallback( );     t.ProcessSomething( ); } 

This method displays the following:

 Finished processing 0 items Finished processing 10 items Finished processing 20 items Finished processing 30 items Finished processing 40 items Finished processing 50 items Finished processing 60 items Finished processing 70 items Finished processing 80 items Finished processing 90 items Processing complete Finished processing 0 items Finished processing 10 items Finished processing 20 items Finished processing 30 items Finished processing 40 items Finished processing 50 items Finished processing 60 items Finished processing 70 items Finished processing 80 items Finished processing 90 items Processing complete 

The current Task class shown in Example 3-13 is designed to allow only a single notification client to be used; in many cases, this would be a severe limitation. The Task class could be modified to handle multiple callbacks, similar to a multicast delegate. The MultiTask class is a modification of the Task class to do just this.

Example 3-13. Handling multiple callbacks

 public class MultiTask {     public MultiTask(NotifyClient notifyClient)     {         notificationObjs.Add(notifyClient);     }     ArrayList notificationObjs = new ArrayList( );     public void AttachToCallback(NotifyClient notifyClient)     {         notificationObjs.Add(notifyClient);     }     public void UnAttachCallback(NotifyClient notifyClient)     {         notificationObjs.Remove(notifyClient);     }     public void UnAttachAllCallbacks( )     {         notificationObjs.Clear( );     }     public void ProcessSomething( )     {         // This method could be any type of processing.                for (int counter = 0; counter < 100; counter++)         {             if ((counter % 10) == 0)             {                 foreach (NotifyClient callback in notificationObjs)                 {                     callback.FinishedProcessingSubGroup(counter);                 }             }         }         foreach (NotifyClient callback in notificationObjs)         {             callback.FinishedProcessingGroup( );         }     } } 

The MultiCallBackThroughIFace method uses callback features of the MultiTask class as follows:

 public void MultiCallBackThroughIFace( ) {     NotifyClient notificationObj = new NotifyClient( );     MultiTask t = new MultiTask(notificationObj);     t.ProcessSomething( );     Console.WriteLine( );     t.AttachToCallback(notificationObj);     t.ProcessSomething( );     Console.WriteLine( );     t.UnAttachCallback(notificationObj);     t.ProcessSomething( );     Console.WriteLine( );     t.UnAttachAllCallbacks( );     t.ProcessSomething( ); } 

This method displays the following:

 Finished processing 0 items Finished processing 10 items Finished processing 20 items Finished processing 30 items Finished processing 40 items Finished processing 50 items Finished processing 60 items Finished processing 70 items Finished processing 80 items Finished processing 90 items Processing complete Finished processing 0 items Finished processing 0 items Finished processing 10 items Finished processing 10 items Finished processing 20 items Finished processing 20 items Finished processing 30 items Finished processing 30 items Finished processing 40 items Finished processing 40 items Finished processing 50 items Finished processing 50 items Finished processing 60 items Finished processing 60 items Finished processing 70 items Finished processing 70 items Finished processing 80 items Finished processing 80 items Finished processing 90 items Finished processing 90 items Processing complete Processing complete Finished processing 0 items Finished processing 10 items Finished processing 20 items Finished processing 30 items Finished processing 40 items Finished processing 50 items Finished processing 60 items Finished processing 70 items Finished processing 80 items Finished processing 90 items Processing complete 

Another shortcoming exists with both the Task and MultiTask classes. What if you need several types of client notification classes? For example, you already have the NotifyClient class. What if you add a second class, NotifyClientType2, which also implements the INotificationCallbacks interface? This new class is shown here:

 public class NotifyClientType2 : INotificationCallbacks {     public void FinishedProcessingSubGroup(int amount)     {         Console.WriteLine("[Type2] Finished processing " + amount + " items");     }     public void FinishedProcessingGroup( )     {         Console.WriteLine("[Type2] Processing complete");     } } 

The current code base cannot handle this new client notification type. To fix this problem, you can replace all occurrences of the type NotifyClient with the interface type INotificationCallbacks. This allows you to use any type of notification client with your Task and MultiTask objects. The modifications to these classes are highlighted in Example 3-14.

Example 3-14. Using multiple notification clients

 public class Task {     public Task(INotificationCallbacks notifyClient)     {         notificationObj = notifyClient;     }          INotificationCallbacks notificationObj = null;     public void AttachToCallback(INotificationCallbacks notifyClient)     {         notificationObj = notifyClient;     }     … } public class MultiTask {     public MultiTask(INotificationCallbacks notifyClient)     {         notificationObjs.Add(notifyClient);     }     ArrayList notificationObjs = new ArrayList( );     public void AttachToCallback(INotificationCallbacks notifyClient)     {         notificationObjs.Add(notifyClient);     }     public void UnAttachCallback(INotificationCallbacks notifyClient)     {         notificationObjs.Remove(notifyClient);     }     …     public void ProcessSomething( )     {         // This method could be any type of processing.         for (int counter = 0; counter < 100; counter++)         {             if ((counter % 10) == 0)             {                 foreach (INotificationCallbacks callback in notificationObjs)                 {                     callback.FinishedProcessingSubGroup(counter);                 }             }         }         foreach (INotificationCallbacks callback in notificationObjs)        {             callback.FinishedProcessingGroup( );        }     } } 

Now you can use either of the client-notification classes interchangeably. This is shown in Example 3-15 in the modified methods MultiCallBackThroughIFace and CallBackThroughIFace.

Example 3-15. Using client notification classes interchangeably

  public void CallBackThroughIFace( ) {     INotificationCallbacks notificationObj = new NotifyClient( );     Task t = new Task(notificationObj);     t.ProcessSomething( );     Console.WriteLine( );     t.UnAttachCallback( );     t.ProcessSomething( );     Console.WriteLine( );          INotificationCallbacks notificationObj2 = new NotifyClientType2( );     t.AttachToCallback(notificationObj2);     t.ProcessSomething( );     Console.WriteLine( );     t.UnAttachCallback( );     t.ProcessSomething( ); }  public void MultiCallBackThroughIFace( ) {     INotificationCallbacks notificationObj = new NotifyClient( );     MultiTask t = new MultiTask(notificationObj);     t.ProcessSomething( );     Console.WriteLine( );          INotificationCallbacks notificationObj2 = new NotifyClientType2( );     t.AttachToCallback(notificationObj2);     t.ProcessSomething( );     Console.WriteLine( );       t.UnAttachCallback(notificationObj);     t.ProcessSomething( );     Console.WriteLine( );     t.UnAttachAllCallbacks( );     t.ProcessSomething( ); } 

The highlighted code has been modified from the original code.

Discussion

Using an interface mechanism for callbacks is a simple but effective alternative to using delegates. The interface mechanism is only slightly faster than using a delegate since you are simply making a call through an interface.

This interface mechanism requires a notification client (NotifyClient) that implements a callback interface (INotificationCallbacks) to be created. This notification client is then passed to an object that is required to call back to this client. This object is then able to store a reference to the notification client and use it appropriately whenever its callback methods are used.

When using the callback methods on the notificationObj, you should test to determine whether the notificationObj is null; if so, you should not use it or else a NullReferenceException will be thrown:

 if (notificationObj != null) {     notificationObj.FinishedProcessingGroup( ); } 

Interface callbacks cannot always be used in place of delegates. The following list indicates where to use each type of callback:

  • Use a delegate if you require ease of coding over performance.

  • Use the interface callback mechanism if you need potentially complex callbacks. An example of this could be adding an interface with a single callback method that will be used to call back into an overloaded method. The number and types of parameters determine the method chosen.

  • You need to perform a number of operations, not just a single operation (e.g., calling method1 to do some work, then calling method2 to do some more work, etc.).

See Also

See the "Interface Keyword," "Base Class Usage Guidelines," and "When to Use Interfaces" topics in the MSDN documentation.



C# Cookbook
Secure Programming Cookbook for C and C++: Recipes for Cryptography, Authentication, Input Validation & More
ISBN: 0596003943
EAN: 2147483647
Year: 2004
Pages: 424

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net