ONDotNet.com    
 Published on ONDotNet.com (http://www.ondotnet.com/)
 See this if you're having trouble printing code examples


Understanding the Nuances of Delegates in C#

by Satya Komatineni
11/04/2002

Delegates

C# introduced a keyword called delegate for utilizing such things as function pointers and call backs. The syntax of a delegate could be confusing, particularly if you have just started using them. One sure way to get latched on to the syntactical nuances of delegates is to understand a delegate's dual nature. Perhaps you can recall from high-school days that matter can exhibit the qualities of both a particle and a wave. I am hoping to convey by the end of this article that, in a similar sense, a delegate exhibits the qualities of a class and a function. Unlike the example drawn from physics, a delegate is a manmade concept and when we understand its background, the mystery shall be obvious.

This article is a critical look at delegates aimed at understanding, rather than an introductory discourse. The reader is advised to use other references for the details. Going through the introductory texts, you may come away with the impression that delegates are like functions and that you can treat them like functions. This emphasis on functions will only muddle the syntactic clarity you need to be productive, because a delegate has more things in common (syntactically) with a class than with a function. Here are some other interesting facts about delegates:

Related Reading

Programming C#
By Jesse Liberty

To delve into each of these assertions, let me take your through a bit of sample code first, followed by commentary on each section of the sample code. The sample code is complete enough to compile and test.


using System;
namespace DelegatesSample
{
  // Define a delegate. class definition
  public delegate bool SampleDelegate(string name);

  class SampleDelegateDriver
  {
    // A delegate variable declaration
    private SampleDelegate m_sampleDelegate;

    // You need this function to construct
    // your delegate object
    private bool log(string text)
    {
      Console.WriteLine(text);
      return true;
    }

    public void start()
    {
      Console.WriteLine("Testing a sample delegate");

      // Instantiating a simple delegate
      m_sampleDelegate = new SampleDelegate(log);

      // Calling a simple delegate
      bool returnCode
        = m_sampleDelegate("A simple delegate call");

      // Combining delegates
      SampleDelegate d1 = new SampleDelegate(log);
      SampleDelegate d2 = new SampleDelegate(log);
      SampleDelegate compositeDelegate = 
        (SampleDelegate)Delegate.Combine(d1, d2);

      //calling a composite delegate
      returnCode = 
        compositeDelegate("Sample composite delegate");

      // operator overloading
      compositeDelegate = d1 + d2;
      returnCode = 
        compositeDelegate("Composite delegate," + 
                          "using the + operator");

      // you can also remove delegates
      // delegates are immutable
      SampleDelegate resultingDelegate = 
        (SampleDelegate)Delegate.Remove(compositeDelegate,
                                        d2);
        
      compositeDelegate("Composite delegate" + 
                        " after removing one of them");
      resultingDelegate("Resulting delegate" + 
                        " after removing one of them");

      //target of a static method
      if (m_sampleDelegate.Target == null)
      {
        log("This delegate is pointing to a static method");
      }
      
      // target of an instance method
      log("Casting the target to its object type");
      SampleDelegateDriver sdd 
        = (SampleDelegateDriver)m_sampleDelegate.Target;

      // Enquire targets type name
      log("Name of the class that is " + 
        "implementing the sample delegate: " 
        + m_sampleDelegate.Target.GetType().FullName);

      // Finally see if the ToString method 
      // of your class is called
      // when invoked on the target instance
      Console.WriteLine("TOSTR:" + 
                        m_sampleDelegate.Target.ToString());

      // walking through the delegate list
      log("Testing GetInvocation list");

      compositeDelegate = d1 + d2;

      int i=0;
      foreach(Delegate x in 
              compositeDelegate.GetInvocationList())
      {
        log("delegate " + i + x.Method.ToString());
        i++;
      }
    }

    // Method to test the target property of a delegate
    override public string ToString()
    {
      return "ToString called";
    }

    static void Main(string[] args)
    {
        SampleDelegateDriver sdd = 
                             new SampleDelegateDriver();
        sdd.start();
    }
  }
}

Syntax For Declaring a Delegate

Let us start our analysis of the sample code by looking at the first few lines of the sample code, where our SampleDelegate is declared.


namespace DelegatesSample
{
  public delegate bool SampleDelegate(String name);

  public class AnotheClass{}
}

Although a delegate is intended to be used as a function pointer (and declared as such, meaning a function), in reality this declaration is a class declaration in the disguise of function-like declaration. We can immediately see, in the same namespace, the declaration of other sibling classes. So if the contention that a delegate is a class were true, we should be able to draw parallels to a class usage and see if we can use the delegate under similar circumstances.

For one thing, we can declare a class inside of another class. This is allowed for delegates as well. So we can have a delegate declared either inside of a class or outside of a class just like any other class.

Underneath the covers, the above delegate declaration is actually translated into a class, as follows


public class SampleDelegate : System.MulticastDelegate {..}

The generated class is an internal class that we won't see except with some debugging tools. But for all practical purposes, we can use it as if it is a real class. 

We Can Declare a Delegate The Same as Any Other Typed Variable

Because a delegate is a class, we should be able to declare variables of this class type. Here is an example from our sample program:


namespace DelegatesSample
{
  public delegate bool SampleDelegate(string name);
  class SampleDelegateDriver
  {
    // One can maintain variables of the above defined 
    // delegate type
    private SampleDelegate m_sampleDelegate;
    ... other stuff
  }
...
}  

See how the word SampleDelegate is used like a class in the variable declaration.

You Can Instantiate a Delegate Because it is a Class

Do we hear that we can instantiate a function? Usually not. But we can instantiate a delegate, because it is a class. Let us visit the code example to see this:


namespace DelegatesSample
{
  public delegate bool SampleDelegate(string name);
  class SampleDelegateDriver
  {
    // some function
    {
      ....
      
      // One can instantiate a delegate using a method
      m_sampleDelegate = new SampleDelegate(log);
      ...
      
    }
    // One will need a method to instantiate a delegate
    private bool log(string message){...}
  }
...
}  

Lot of things are going on in this code segment. We can see that the SampleDelegate is instantiated and set to a local variable of that type. Input to this instantiation process is a function name called log. What is log? log is a function defined within the same class. In addition, this function has input and output arguments that match the original delegate declaration. The construction of a delegate is where we are binding the function to the delegate class.

This binding is very powerful in languages like C#, where ownership is carried out intrinsically (a.k.a. garbage collection). Before I explain that statement, let's consider the nature of the log function. The log function is an instance method, meaning its existence depends on the lifetime of the class of which it is a member; in this case, the SampleDelegateDriver class. The delegates also allow you to bind static functions. Static functions hang around all the time, so there is no problem when we pass around our delegate. But what happens when we load up our delegate with an instance function and pass it around while our object goes out of scope? This would be a major issue in C++, but isn't in C#, because C# will keep reference to this instance class, as long as its method is held by a delegate. This observation is important for designing delegates.

We Can Pass a Delegate Variable Around as an Argument

Once we have a variable declared as a delegate, we can pass that delegate around as if it were a variable with an appropriate type. The sample code doesn't have an instance of this usage, so let me put up a small example:


namespace SomeNameSpace
{
  public static somefunction(SampleDelegate d)
  {
    d("log this message);
  }
}

Related Reading

C# in a Nutshell
By Peter Drayton, Ben Albahari, Ted Neward

The beauty of a delegate is very obvious here. One would have thought in a doubt-filled haste that while defining a function that takes a delegate argument, we would have to specify the input and output parameters of that delegate. But that aspect is nicely abstracted out into the delegate declaration already.

Let Us Call the Delegate

We have defined a delegate, instantiated it, and passed it around, but how do we call it? This is where a delegate works like a function. Let us borrow a few lines from the sample code:


// Calling a simple delegate
bool returnCode
  = m_sampleDelegate("A simple delegate call");

As we know, the local variable m_sampleDelegate is loaded with a function called log. This log function, when invoked, will write to console the passed-in text message. Because log is bound to m_sampleDelegate, you can use m_sampleDelegate as if it were a log function. Following that thought, we should see the log method invoked and the text written to console when the above sample code is executed. The return code from the log method will be returned as the output of the delegate call.

You Can Call Two Delegates With One Call: Combining Delegates

Here is where, in my mind, delegates truly break out of the mold and touch a new level of abstraction. Multiple delegates can be composed into one. The composed delegate can be used as if it is a single function call. Let us lead with an example borrowed from the sample code:


// Combining delegates
SampleDelegate d1 = new SampleDelegate(log);
SampleDelegate d2 = new SampleDelegate(log);
SampleDelegate compositeDelegate = 
  (SampleDelegate)Delegate.Combine(d1, d2);

where d1 and d2 are two delegates happens to be pointing to the same log function. Using the static method on the Delegate class, we can create a new delegate called compositeDelegate (bearing the same signature) by combining d1 and d2. Now when we call the composite delegate, we will will see that the log method is called twice and the output written twice to the console. Here is the method invocation for the composite delegate:


returnCode = 
  compositeDelegate("Sample composite delegate");

Calling the composite delegate is the same as calling any other delegate. The immediate question, then, is: can one combine these delegates dynamically at run time? I will leave that to the reader to investigate. Nevertheless, I will report on a shortcut for combining delegates based on overloading. Here are a couple of lines borrowed from the sample:

  compositeDelegate = d1 + d2;
returnCode = 
  compositeDelegate("Sample composite delegate, " + 
                    "using the + operator");

Where d1 and d2 are the delegates that were previously defined and shown. Again, calling the composite delegate is no different. I will leave it to the reader again to see if other overloaded operators exist.

You Can Remove Delegates From a Composite Delegate

It is only deductive that one should look for the possibility of removing delegates from a composite delegate. The following code does exactly that. But, unlike the case of composition, the removal syntax raises an important doubt. Let us remember to examine that doubt.

  
SampleDelegate resultingDelegate = 
     (SampleDelegate)Delegate.Remove(compositeDelegate,d2);
compositeDelegate("Sample composite delegate after " + 
                  "removing one of them");
resultingDelegate("Resulting Sample composite delegate " +
                  "after removing one of them");

It is not difficult to wrongly conclude that after the removal, the composite delegate will have only one delegate left in its belly. Just to verify that, we have used the compositeDelegate in a subsequent call to see what it prints. For the record, it prints the message twice, while the resultingDelegate prints it only once. This experiment gives us another important rule of thumb as far as delegates go: delegates are immutable. You can compose new ones out of them or par them down into new ones, but original ones remain casted (pun nevertheless, but unintended).

Retrieving the Target Class From a Delegate

As we have been covering so far, a delegate points to a function. And this function can be either a static function or an instance function belonging to an object of a class. It is possible to retrieve this target at run time if needed. For example, you can retrieve a target from a delegate, cast the target to an appropriate class, and call the additional methods on that object. If the delegate points to a static method, then the target points to null. Let us examine the code that exercises this functionality:

  
if (m_sampleDelegate.Target == null)
{
  log("This delegate is pointing to a static method");
}

SampleDelegateDriver sdd = 
             (SampleDelegateDriver)m_sampleDelegate.Target;

Console.WriteLine("TOSTR:" + 
                  m_sampleDelegate.Target.ToString());

The first part of this sample tests to see if a target exists. If the target does not exist, the implication is that the delegate is pointing to a static method. The second part obtains the target of the m_sampleDelegate, which is the object SampleDelegateDriver itself. And we can call the toString method on that object to prove that it is actually pointing to that object.

If A Delegate Was a Class, What Other Methods Does It Have?

If a delegate was a function, should it be surprising to us that we were able to do m_sampleDelegate.Target? We know functions don't have attributes; again, this is possible because it is a class. As a result, it has more methods than we normally use a delegate for. In fact, some of them we have already seen (Combine() and Remove), but they happen to be class-level static methods. An instance method of a delegate that is useful is GetInvocationList(). This method allows us to iterate through the individual delegates of a composite delegate. The following code demonstrates this:

  
compositeDelegate = d1 + d2;
int i=0;
foreach(Delegate x in compositeDelegate.GetInvocationList())
{
  log("delegate " + i + x.Method.ToString());
  i++;
}

A Few Notes on Delegate Composition

When we combine delegates, it is clear that they all have to have the same signature. When a composite delegate is called, the return value of the last delegate is returned as the return value of the composite. The intermediate return values are ignored. Also, when one of the delegates throws an exception, the processing is discontinued and the exception returned to the caller. This is not untypical of the pipeline processing that is the cornerstone of delegates. Again I will have to leave it to the reader to investigate if there are any options that one can set on a delegate to indicate to ignore exceptions and continue. If a removal operation on a composite delegate removes the last delegate, then the caller will get back null.

Conclusion

Delegates are routinely used in the publish/subscribe programming model in .NET. Delegates are also used in asynchronous programming models in .NET. Both subjects are fairly large, and I won't be able to cover them here. I encourage the reader to look at them as a followup for the material presented here.

References

I have largely depended on the documentation that comes with VisualStudio.NET. I have used the following keywords: "Delegate class c#". You may be able to locate this information on MSDN online as well.

Satya Komatineni is the CTO at Indent, Inc. and the author of Aspire, an open source web development RAD tool for J2EE/XML.


Return to ONDotnet.com

Copyright © 2009 O'Reilly Media, Inc.