C Sharp

Using Delegates as Callback Methods

Used extensively in programming for Microsoft Windows, callback methods are used when you need to pass a function pointer to another function that will then call you back (via the passed pointer). An example would be the Win32 API EnumWindows function. This function enumerates all the top-level windows on the screen, calling the supplied function for each window. Callbacks serve many purposes, but the following are the most common: -

  • Asynchronous processing Callback methods are used in asynchronous processing when the code being called will take a good deal of time to process the request. Typically, the scenario works like this: Client code makes a call to a method, passing to it the callback method. The method being called starts a thread and returns immediately. The thread then does the majority of the work, calling the callback function as needed. This has the obvious benefit of allowing the client to continue processing without being blocked on a potentially lengthy synchronous call.
  • Injecting custom code into a class's code path Another common use of callback methods is when a class allows the client to specify a method that will be called to do custom processing. Let's look at an example in Windows to illustrate this. Using the Listbox class in Windows, you can specify that the items be sorted in ascending or descending order. Besides some other basic sort options, the Listbox class can't really give you any latitude and remain a generic class. Therefore, the Listbox class also enables you to specify a callback function for sorting. That way, when Listbox sorts the items, it calls the callback function and your code can then do the custom sorting you need.

Now let's look at an example of defining and using a delegate.In this example, we have a database manager class that keeps track of all active connections to the database and provides a method for enumerating those connections. Assuming that the database manager is on a remote server, it might be a good design decision to make the method asynchronous and allow the client to provide a callback method. Note that for a real-world application you'd typically create this as a multithreaded application to make it truly asynchronous. However, to keep the example simple-and because we haven't covered multithreading yet-let's leave multithreading out.

First, let's define two main classes: DBManager and DBConnection.

class DBConnection
{
    image
}
  class DBManager
  {
    static DBConnection[] activeConnections;
    image
    public delegate void EnumConnectionsCallback(DBConnection connection);
      public static void EnumConnections(EnumConnectionsCallback callback)
      {
          foreach (DBConnection connection in activeConnections)
          {
              callback(connection);
          }
      }
  }

The EnumConnectionsCallback method is the delegate and is defined by placing the keyword delegate in front of the method signature. You can see that this delegate is defined as returning void and taking a single argument: a DBConnection object. The EnumConnections method is then defined as taking an EnumConnectionsCallback method as its only argument. To call the DBManager. EnumConnections method, we need only pass to it an instantiated DBManager. EnumConnectionCallback delegate.

To do that, you new the delegate, passing to it the name of method that has the same signature as the delegate. Here's an example of that: -

DBManager.EnumConnectionsCallback myCallback =
     new DBManager.EnumConnectionsCallback(ActiveConnectionsCallback);
DBManager.EnumConnections(myCallback);

Also note that you can combine this into a single call like so: -

DBManager.EnumConnections(new
    DBManager.EnumConnectionsCallback(ActiveConnectionsCallback));

That's all there is to the basic syntax of delegates. Now let's look at the full example application: -

using System;
class DBConnection
{
    public DBConnection(string name)
    {
        this.name = name;
    }
      protected string Name;
      public string name
      {
          get
          {
              return this.Name;
          }
          set
          {
              this.Name = value;
          }
      }
  }
  class DBManager
  {
      static DBConnection[] activeConnections;
      public void AddConnections()
      {
          activeConnections = new DBConnection[5];
          for (int i = 0; i < 5; i++)
          {
              activeConnections[i] =
new DBConnection("DBConnection " + (i + 1));
          }
      }
      public delegate void EnumConnectionsCallback(DBConnection connection);
      public static void EnumConnections(EnumConnectionsCallback callback)
      {
          foreach (DBConnection connection in activeConnections)
          {
              callback(connection);
          }
      }
  }
  class Delegate1App
  {
      public static void ActiveConnectionsCallback(DBConnection connection)
      {
          Console.WriteLine("Callback method called for "
                            + connection.name);
      }
      public static void Main()
      {
           DBManager dbMgr = new DBManager();
           dbMgr.AddConnections();
      DBManager.EnumConnectionsCallback myCallback =
      new DBManager.EnumConnectionsCallback(ActiveConnectionsCallback);
      DBManager.EnumConnections(myCallback);
      }
}

Compiling and executing this application results in the following output: -

Callback method called for DBConnection 1
Callback method called for DBConnection 2
Callback method called for DBConnection 3
Callback method called for DBConnection 4
Callback method called for DBConnection 5