Pages

Advertisement

Thursday, July 12, 2007

.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.


(
Full Size Image)

Figure 1. Output from Delegate Sample Code

 


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.


(
Full Size Image)

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.

No comments:

Post a Comment