Section 12.5. Retrieving Values from Multicast Delegates


12.5. Retrieving Values from Multicast Delegates

In most situations, the methods you'll encapsulate with a multicast delegate will return void. In fact, the most common use of multicast delegates is with events, and you will remember that by convention, all events are implemented by delegates that encapsulate methods that return void (and also take two parameters: the sender and an EventArgs object).

It is possible, however, to create multicast delegates for methods that don't return void. In the next example, you will create a very simple test class with a delegate that encapsulates any method that takes no parameters but returns an integer:

public class ClassWithDelegate {   public delegate int DelegateThatReturnsInt();   public DelegateThatReturnsInt theDelegate;

To test this, you implement two classes that subscribe to your delegate. The first encapsulates a method that increments a counter and returns that value as an integer:

public class FirstSubscriber {   private int myCounter = 0;   public void Subscribe(ClassWithDelegate theClassWithDelegate)   {     theClassWithDelegate.theDelegate +=        new ClassWithDelegate.DelegateThatReturnsInt(DisplayCounter);   }   public int DisplayCounter( )   {     return ++myCounter;   } }

The second class also maintains a counter, but its delegated method doubles the counter and returns that doubled value:

public class SecondSubscriber {   private int myCounter = 0;   public void Subscribe(ClassWithDelegate theClassWithDelegate)   {     theClassWithDelegate.theDelegate +=        new ClassWithDelegate.DelegateThatReturnsInt(Doubler);   }   public int Doubler( )   {     return myCounter += 2;   } }

When you fire this delegate, each encapsulated method is called in turn, and each returns a value:

int result = theDelegate(); Console.WriteLine(   "Delegates fired! Returned result: {0}",   result);

The problem is that as each method returns its value, it overwrites the value assigned to result. The output looks like this:

Delegates fired! Returned result: 2 Delegates fired! Returned result: 4 Delegates fired! Returned result: 6 Delegates fired! Returned result: 8 Delegates fired! Returned result: 10

The first method, DisplayCounter( ) (which was called by FirstSubscriber), returned the values 1,2,3,4,5, but these values were overwritten by the values returned by the second method.

Your goal is to display the result of each method invocation in turn. To do so, you must take over the responsibility of invoking the methods encapsulated by your multicast delegate. You do so by obtaining the invocation list from your delegate and explicitly invoking each encapsulated method in turn:

foreach (    DelegateThatReturnsInt del in      theDelegate.GetInvocationList() ) {   int result = del( );   Console.WriteLine(     "Delegates fired! Returned result: {0}",     result); } Console.WriteLine( );

This time, result is assigned the value of each invocation, and that value is displayed before invoking the next method. The output reflects this change:

Delegates fired! Returned result: 1 Delegates fired! Returned result: 2 Delegates fired! Returned result: 2 Delegates fired! Returned result: 4 Delegates fired! Returned result: 3 Delegates fired! Returned result: 6 Delegates fired! Returned result: 4 Delegates fired! Returned result: 8 Delegates fired! Returned result: 5 Delegates fired! Returned result: 10

The first delegated method is counting up (1,2,3,4,5) while the second is doubling (2,4,6,8,10). The complete source is shown in Example 12-5.

Example 12-5. Invoking delegated methods manually
#region Using directives using System; using System.Collections.Generic; using System.Text; using System.Threading; #endregion namespace InvokingDelegatedMethodsManually {    public class ClassWithDelegate    {       // a multicast delegate that encapsulates a method       // that returns an int       public delegate int DelegateThatReturnsInt( );       public DelegateThatReturnsInt theDelegate;       public void Run( )       {          for ( ; ; )          {             // sleep for a half second             Thread.Sleep( 500 );             if ( theDelegate != null )             {                // explicitly invoke each delegated method                foreach (                  DelegateThatReturnsInt del in                    theDelegate.GetInvocationList( ) )                {                   int result = del( );                   Console.WriteLine(                     "Delegates fired! Returned result: {0}",                     result );                }  // end foreach                Console.WriteLine( );             }    // end if          }      // end for ;;       }        // end run    }          // end class    public class FirstSubscriber    {       private int myCounter = 0;       public void Subscribe( ClassWithDelegate theClassWithDelegate )       {          theClassWithDelegate.theDelegate +=            new ClassWithDelegate.DelegateThatReturnsInt( DisplayCounter );       }       public int DisplayCounter( )       {          return ++myCounter;       }    }    public class SecondSubscriber    {       private int myCounter = 0;       public void Subscribe( ClassWithDelegate theClassWithDelegate )       {          theClassWithDelegate.theDelegate +=            new ClassWithDelegate.DelegateThatReturnsInt( Doubler );       }       public int Doubler( )       {          return myCounter += 2;       }    }    public class Test    {       public static void Main( )       {          ClassWithDelegate theClassWithDelegate =            new ClassWithDelegate( );          FirstSubscriber fs = new FirstSubscriber( );          fs.Subscribe( theClassWithDelegate );          SecondSubscriber ss = new SecondSubscriber( );          ss.Subscribe( theClassWithDelegate );          theClassWithDelegate.Run( );       }    } }

12.5.1. Invoking Events Asynchronously

It may turn out that the event handlers take longer than you like to respond to the event. In that case, it may take a while to notify later handlers, while you wait for results from earlier handlers. For example, suppose the DisplayCounter( ) method in FirstSubscriber needs to do a lot of work to compute the return result. This would create a delay before SecondSubscriber was notified of the event. You can simulate this by adding a few lines to DisplayCounter:

public int DisplayCounter() {   Console.WriteLine("Busy in DisplayCounter...");   Thread.Sleep(4000);   Console.WriteLine("Done with work in DisplayCounter...");   return ++myCounter; }

When you run the program, you can see the four-second delay each time FirstSubscriber is notified. An alternative to invoking each method through the delegates (as shown earlier) is to call the BeginInvoke( ) method on each delegate. This will cause the methods to be invoked asynchronously, and you can get on with your work, without waiting for the method you invoke to return.

Unlike Invoke(), BeginInvoke() returns immediately. It creates a separate thread in which its own work is done.[2] (For more information about threads, see Chapter 20.)

[2] .NET provides thread pooling, and the "new" thread will typically be pulled from the pool.

This presents a problem, however, since you do want to get the results from the methods you invoke. You have two choices. First, you can constantly poll each delegated method, asking if it has a result yet. This would be like asking your assistant to do some work for you and then telephoning every five seconds saying, "Is it done yet?" (a waste of everybody's time). What you want to do is to turn to your assistant and say, "Do this work, and call me when you have a result."

12.5.2. Callback Methods

You accomplish this goal of delegating work and being called back when it is done with a callback, which you implement with (surprise!) a delegate. The .NET Framework provides a callback mechanism by defining the ASyncCallBack delegate:

[Serializable] public delegate void AsyncCallback(    IAsyncResult ar );

The attribute (Serializable) is covered in Chapter 18. You can see here, however, that AsyncCallBack is a delegate for a method that returns void and takes a single argument, an object of type IAsyncResult. This interface is defined by the Framework, and the CLR will be calling your method with an object that implements the interface, so you don't need to know the details of the interface; you can just use the object provided to you.

Here's how it works. You will ask the delegate for its invocation list, and you will call BeginInvoke on each delegate in that list. BeginInvoke will take two parameters. The first will be a delegate of type AsyncCallBack, and the second will be your own delegate that invokes the method you want to call:

del.BeginInvoke(new AsyncCallback(ResultsReturned),del);

In the line of code shown here, you are calling the method encapsulated by del (e.g., DisplayCounter) and when that method completes, you want to be notified via your method ResultsReturned.

The method to be called back (ResultsReturned) must match the return type and signature of the AsyncCallback delegate: it must return void and must take an object of type IAsyncResult:

private void ResultsReturned(IAsyncResult iar) {

When that method is called back, the IAsyncResult object is passed in by the .NET Framework. The second parameter to BeginInvoke is your delegate, and that delegate is stashed away for you in the AsyncState property of the IAsyncResult as an Object. Inside the ResultsReturned callback method, you can extract that Object and cast it to its original type:

DelegateThatReturnsInt del = (DelegateThatReturnsInt)iar.AsyncState;

You can now use that delegate to call the EndInvoke( ) method, passing in the IAsyncResult object you received as a parameter:

int result = del.EndInvoke(iar);

EndInvoke() returns the value of the called (and now completed) method, which you assign to a local variable named result, and which you are now free to display to the user.

The net effect is that in Run(), you get each registered method in turn (first FirstSubscriber.DisplayCounter and then SecondSubscriber.Doubler), and you invoke each asynchronously. There is no delay between the call to the first and the call to the second, as you aren't waiting for DisplayCounter to return.

When DisplayCounter (or Doubler) has results, your callback method (ResultsReturned) is invoked, and you use the IAsyncResult object provided as a parameter to get the actual results back from these methods. The complete implementation is shown in Example 12-6.

Example 12-6. Asynchronous invocation of delegates
#region Using directives using System; using System.Collections.Generic; using System.Text; using System.Threading; #endregion namespace AsynchDelegates {    public class ClassWithDelegate    {       // a multicast delegate that encapsulates a method       // that returns an int       public delegate int DelegateThatReturnsInt( );       public event DelegateThatReturnsInt theDelegate;       public void Run( )       {          for ( ; ; )          {             // sleep for a half second             Thread.Sleep( 500 );             if ( theDelegate != null )             {                // explicitly invoke each delegated method                foreach (                  DelegateThatReturnsInt del in                    theDelegate.GetInvocationList( ) )                {                   // invoke asynchronously                   // pass the delegate in as a state object                   del.BeginInvoke( new AsyncCallback( ResultsReturned ),                        del );                }  // end foreach             }    // end if          }      // end for ;;       }        // end run       // call back method to capture results       private void ResultsReturned( IAsyncResult iar )       {          // cast the state object back to the delegate type          DelegateThatReturnsInt del =             ( DelegateThatReturnsInt ) iar.AsyncState;          // call EndInvoke on the delegate to get the results          int result = del.EndInvoke( iar );          // display the results          Console.WriteLine( "Delegate returned result: {0}", result );       }    }          // end class    public class FirstSubscriber    {       private int myCounter = 0;       public void Subscribe( ClassWithDelegate theClassWithDelegate )       {          theClassWithDelegate.theDelegate +=            new ClassWithDelegate.DelegateThatReturnsInt( DisplayCounter );       }       public int DisplayCounter( )       {          Console.WriteLine( "Busy in DisplayCounter..." );          Thread.Sleep( 10000 );          Console.WriteLine( "Done with work in DisplayCounter..." );          return ++myCounter;       }    }    public class SecondSubscriber    {       private int myCounter = 0;       public void Subscribe( ClassWithDelegate theClassWithDelegate )       {          theClassWithDelegate.theDelegate +=            new ClassWithDelegate.DelegateThatReturnsInt( Doubler );       }       public int Doubler( )       {          return myCounter += 2;       }    }    public class Test    {       public static void Main( )       {          ClassWithDelegate theClassWithDelegate =            new ClassWithDelegate( );          FirstSubscriber fs = new FirstSubscriber( );          fs.Subscribe( theClassWithDelegate );          SecondSubscriber ss = new SecondSubscriber( );          ss.Subscribe( theClassWithDelegate );          theClassWithDelegate.Run( );       }    } }



Programming C#(c) Building. NET Applications with C#
Programming C#: Building .NET Applications with C#
ISBN: 0596006993
EAN: 2147483647
Year: 2003
Pages: 180
Authors: Jesse Liberty

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