Recipe 3.18. Adding a Notification Callback Using an InterfaceProblemYou 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. SolutionUse 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
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
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
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
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
The highlighted code has been modified from the original code. DiscussionUsing 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:
See AlsoSee the "Interface Keyword," "Base Class Usage Guidelines," and "When to Use Interfaces" topics in the MSDN documentation. |