.NET Delegates: Modern-Day Callback Methods

Delegates became more mainstream to the Microsoft programming world when Microsoft included them in the .NET Framework, but they actually have been around in another form for a while. Many programmers will recognize them as callback functions/methods. In fact, a brief discussion regarding the purpose of callback methods before jumping into the specifics of delegates will make delegates easier to understand.

Let's define callbacks through an example rather than a formal definition. Consider a multi-tier application for loan processing that has a business logic component containing loan-processing rules. During processing, a decision tree determines the applicant's overall credit rating score. This calculation is complex, often involving a number of variables and taking varying lengths of time to complete. Suppose that while the application is processing an applicant, a loan officer learns something that requires another applicant's credit score to be updated. Rather than have the UI wait until the processing finishes, which prevents the officer from updating the other applicant's score, the application makes a request and will notify the UI when processing is complete. In order for this notification to occur, the business logic component needs the address of the method to call in the UI when processing is complete. This is the essence of a callback method. It is a pointer to a method in the client to call when processing is complete. For those familiar with design patterns, this is the observer design pattern.

At this point, alarm bells may be going off in the heads of many readers. I used the dreaded pointer word, which has caused headaches for many programmers and application users alike. Pointers represent just an address in memory and nothing about the method being called, not its parameters nor any return value. The called method pulls all of that information off the call stack. If the address location gets moved, a number of potential problems including malicious code can occur. More likely than malicious code, however, .NET's managed environment can move items around in memory as it does its internal optimization. Thus, callback methods aren't type safe and don't fit into the realm of Microsoft .NET.

Enter delegates. A delegate is essentially a type-safe pointer to a method. The .NET runtime enforces a check against the method signature and only the method with the proper signature will execute.

Using Delegates

Microsoft .NET uses the delegate keyword to define a delegate. The syntax looks similar to any other method declaration where it has a return type and parameters.

Delegate sample code

The following sample code contains a simple example of using delegates. The example is based on the aforementioned example scenario. The console application requests that a loan applicant be processed, and then provides a method to call when the processing is complete. In reality, you'd use a forms application, but a simple console will work for this example:

using System;

namespace CodeGuru.Delegates
{
   /// <summary>
   /// Sample class representing a loan applicant.
   /// </summary>
   public class LoanApplicant
   {
      public double CreditScore = 0;
   }

   /// <summary>
   /// Sample class containing a loan processing example.
   /// </summary>
   public class LoanSystem
   {
      // Loan applicant.
      private LoanApplicant _LoanApplicant = new LoanApplicant();

      // Delegate for processing a loan.
      public delegate void ProcessLoanDelegate(LoanApplicant
                                               loanApplicant);

      // Process the loan application.
      public void ProcessLoanApplication(ProcessLoanDelegate
                                         processLoan)
      {
         // Calculate the credit score
         this.CalculateCreditScore(_LoanApplicant);

         // Execute the callback method
         processLoan(_LoanApplicant);
      }

      // Calculate the applicant's credit score.
      private void CalculateCreditScore(LoanApplicant loanApplicant)
      {
         Random randNumber = new Random();
         loanApplicant.CreditScore = randNumber.Next(100) + .5;
      }
   }

   /// <summary>
   /// Sample application to demonstrate the use of delegates.
   /// </summary>
   class DelegateExample
   {
      /// <summary>
      /// The main entry point for the application.
      /// </summary>
      [STAThread]
      static void Main(string[] args)
      {
         DelegateExample example = new DelegateExample();
         LoanSystem loanSystem   = new LoanSystem();
         loanSystem.ProcessLoanApplication(
            new LoanSystem.ProcessLoanDelegate(
               example.DisplayCreditScore));
         Console.ReadLine();
      }


      /// <summary>
      /// Display the loan applicant's credit score.
      /// </summary>
      /// <param name="loanApplicant">Loan applicant</param>
      public void DisplayCreditScore(LoanApplicant loanApplicant)
      {
         Console.WriteLine("Applicant Credit Score: {0}",
         loanApplicant.CreditScore);
      }
   }
}

Output from the example looks similar to Figure 1. The number will vary depending upon what the random number generates.

Figure 1. Output from Delegate Sample Code

.NET Delegates: Modern-Day Callback Methods

Multicast Delegates

You may want to have multiple associated methods that are called. This is known as having multicast delegates. When you have multiple methods, .NET automatically builds a list and calls them in the sequence they were assigned.

Multicast delegates sample code

I've modified the previous sample to demonstrate the use of multicast delegates. It involves nothing more than associating another method for callback. The following example includes another method that displays the processed date after the loan is processed and the credit score is displayed:

using System;

namespace CodeGuru.Delegates
{
   /// <summary>
   /// Sample application to demonstrate the use of delegates.
   /// </summary>
   class DelegateExample
   {
      /// <summary>
      /// The main entry point for the application.
      /// </summary>
      [STAThread]
      static void Main(string[] args)
      {
         DelegateExample example = new DelegateExample();
         LoanSystem loanSystem   = new LoanSystem();
         loanSystem.ProcessLoanApplication(
            new LoanSystem.ProcessLoanDelegate(
               example.DisplayCreditScore));
               loanSystem.ProcessLoanApplication(
                  new LoanSystem.ProcessLoanDelegate(
                     example.DisplayProcessingInfo));
               Console.ReadLine();
      }

      /// <summary>
      /// Display the loan applicant's credit score.
      /// </summary>
      /// <param name="loanApplicant">Loan applicant</param>
      public void DisplayCreditScore(LoanApplicant loanApplicant)
      {
         Console.WriteLine("Applicant Credit Score: {0}",
                           loanApplicant.CreditScore);
      }

      /// <summary>
      /// Display the loan applicant's processing information.
      /// </summary>
      /// <param name="loanApplicant"></param>
      public void DisplayProcessingInfo(LoanApplicant loanApplicant)
      {
         Console.WriteLine("Loan Processed: {0}",
         loanApplicant.ProcessedDate.ToLongDateString());
      }
   }

   /// <summary>
   /// Sample class representing a loan applicant.
   /// </summary>
   public class LoanApplicant
   {
      public double CreditScore = 0;
      public DateTime ProcessedDate;
   }


   /// <summary>
   /// Sample class containing a loan processing example.
   /// </summary>
   public class LoanSystem
   {
      // Loan applicant.
      private LoanApplicant _LoanApplicant = new LoanApplicant();

      // Delegate for processing a loan.
      public delegate void ProcessLoanDelegate(LoanApplicant
                                               loanApplicant);

      // Constructor
      public LoanSystem()
      {
      }

      // Process the loan application.
      public void ProcessLoanApplication(ProcessLoanDelegate
                                         processLoan)
      {
         // Calculate the credit score
         this.CalculateCreditScore(_LoanApplicant);

         // Set the processing information
         _LoanApplicant.ProcessedDate = DateTime.Now;

         // Execute the callback method
         processLoan(_LoanApplicant);
      }

      // Calculate the applicant's credit score.
      private void CalculateCreditScore(LoanApplicant loanApplicant)
      {
         Random randNumber = new Random();
         loanApplicant.CreditScore = randNumber.Next(100) + .5;
      }
   }
}

Output from the modified sample looks similar to Figure 2.

Figure 2. Output from Multicast Delegates Sample Code

Uses for Delegates

Delegates can have a number of uses in your applications. The following list contains a rough description of some of the ways you could put delegates to work for you. The list is by no means a complete list, but it should give you an idea of some different areas in which delegates may be appropriate:

  • They enable callback functionality in multi-tier applications as demonstrated in the examples above.
  • The CacheItemRemoveCallback delegate can be used in ASP.NET to keep cached information up to date. When the cached information is removed for any reason, the associated callback is exercised and could contain a reload of the cached information.
  • Use delegates to facilitate asynchronous processing for methods that do not offer asynchronous behavior.
  • Events use delegates so clients can give the application events to call when the event is fired. Exposing custom events within your applications requires the use of delegates.

Future Columns

The topic of the next column is yet to be determined. If you have something in particular that you would like to see explained here, you can reach me at mstrawmyer@crowechizek.com.



About the Author

Mark Strawmyer

Mark Strawmyer is a Senior Architect of .NET applications for large and mid-size organizations. He specializes in architecture, design and development of Microsoft-based solutions. Mark was honored to be named a Microsoft MVP for application development with C# for the fifth year in a row. You can reach Mark at mark.strawmyer@crowehorwath.com.

Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • On-demand Event Event Date: September 10, 2014 Modern mobile applications connect systems-of-engagement (mobile apps) with systems-of-record (traditional IT) to deliver new and innovative business value. But the lifecycle for development of mobile apps is also new and different. Emerging trends in mobile development call for faster delivery of incremental features, coupled with feedback from the users of the app "in the wild." This loop of continuous delivery and continuous feedback is how the best mobile …

  • Java developers know that testing code changes can be a huge pain, and waiting for an application to redeploy after a code fix can take an eternity. Wouldn't it be great if you could see your code changes immediately, fine-tune, debug, explore and deploy code without waiting for ages? In this white paper, find out how that's possible with a Java plugin that drastically changes the way you develop, test and run Java applications. Discover the advantages of this plugin, and the changes you can expect to see …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds