[ Team LiB ] Previous Section Next Section

12.4 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 do not 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'll 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 of the encapsulated methods will be called in turn, and each will return a value:

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

The problem is that as each method returns its value, it will overwrite the value assigned to result. The output will look 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-6.

Example 12-6. Invoking delegated methods manually
namespace Programming_CSharp
{
  using System;
  using System.Threading;

  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.4.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. (For more information about threads, see Chapter 20; for more about BeginInvoke( ) and EndInvoke( ), see Chapter ?.)

This presents a problem, however, 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.4.2 Call Back 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 call back 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, so you don't have to worry about providing an object that implements this interface.

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 will be 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 returned 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 of the registered methods 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 are not 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-7.

Example 12-7. Asynchronous invocation of delegates
namespace Programming_CSharp
{
  using System;
  using System.Threading;
  

  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( );
    }
  }
}    
    [ Team LiB ] Previous Section Next Section