WindowsDevCenter.com
oreilly.comSafari Books Online.Conferences.

advertisement


AddThis Social Bookmark Button

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:

  • A delegate represents a class.
  • A delegate is type-safe.
  • You can combine multiple delegates into a single delegate.
  • You can use delegates both for static and instance methods.
  • You can define delegates inside or outside of classes.
  • You can use delegates in asynchronous-style programming.
  • Delegates are often used in event-based programming, such as publish/subscribe.

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.

Pages: 1, 2

Next Pagearrow