The Delegate Type in J#
This page is specific to:.NET Framework Version:
1.1
Visual Studio Technical Articles
The Delegate Type in J#
 

Pratap Lakshman
Visual J# .NET Team
Microsoft Corporation
April 2004

Applies to:
   Microsoft® Visual J# .NET

Summary: Use the Delegate Type in Microsoft Visual J# .NET to treat methods as data. (9 printed pages)

Contents

Introduction
Declaring a Delegate Type
Defining a Delegate Handle
Allocating a Delegate Object
Applying the Delegate Object
Delegate and Interfaces
Multicast Delegates
Implementation Support for Delegates
Type Safety, Security, and Verifiability
Type Equivalence
Delegates and Events
Conclusion
References

Introduction

Parameterization by a function arises naturally in a number of contexts:

  • Consider a sorting routine for an array. We might want to sort in ascending order or descending order.
  • Consider removing unwanted items from a linked list. We might wish to vary the filtering criteria from usage to usage.
  • Consider a callback from an event handler. We might wish to customize the behaviour to invoke when the event occurs.

In functional programming languages, functions are first class data objects. For example, in Scheme [1] functions are first class objects in the language—functions can be passed as parameters just as anything else can be—passed as arguments, or returned as results, or stored in variables or fields of other objects. In contrast, in imperative or object-oriented programming languages passing functions (methods) as parameters is unusual, and often inconvenient to express.

Java-language [2] programmers are already familiar with primitive types and class/interface types. J# introduces a new member into this type-family—a method reference—through the .NET delegate type [3]. Delegates enable treating methods (member functions) as data—passed as arguments, returned as results, and stored in variables or fields of other objects.

A delegate encapsulates a method and a reference to a specific object instance. For static methods, the encapsulated object reference will be null. For instance methods, the object reference will be the this of any invocation. Given a delegate instance and an appropriate set of arguments, applying the delegate is the same as applying that method to the arguments.

Declaring a Delegate Type

A delegate declaration is a custom type declaration, and consists of five components:

  1. An optional access level
  2. The keyword delegate
  3. The return type and signature of the method the delegate type addresses
  4. The name of the delegate type
  5. The /** @delegate */ decoration

For example, the following declares Proc to be a public delegate type that addresses methods taking no parameters and with a return type of void.

/** @delegate */
public delegate void Proc();

Although this looks like a method declaration, it is indeed a type declaration.

Note that the declaration of Proc does not refer to any class.

Delegates were first introduced in Visual J++ [4] through the delegate keyword. Decorating the declaration with /** @delegate */ instructs the compiler to create a .NET delegate. J# supports authoring both delegate forms, though this paper discusses only the latter.

Defining a Delegate Handle

Specifically, a delegate type in J# is a reference type. The definition of a reference type is a two-step process:

  1. A named handle that we manipulate directly.
  2. An unnamed object of the handle's type that we manipulate indirectly through the handle. This object must be explicitly created using the new operator.

When we write:

    Proc p1;

p1 represents a handle to a delegate object of the Proc delegate type, but is not itself the delegate object.

Allocating a Delegate Object

Consider the following classes:

public class MyDateClass {
  public void DisplayDate() {
    Date dt = new Date();
    System.out.println("The date is: " + dt.getDate());
  }
}
public class MyTimeClass {
  public static void DisplayTime() {
    Date dt = new Date();
    System.out.println("The time is: " 
       + dt.getHours() + 'h' + ' ' + dt.getMinutes() + 'm');
  }
}

To have p1 encapsulate the DisplayDate method on MyDateClass, we must create an instance of the Proc delegate type using the new operator. The argument to the constructor is the class object through which we wish to invoke the method joined to the method name by the dot '.' operator.

Note that this initialization of the delegate instance at the time of allocation cannot be avoided.

    MyDateClass d = new MyDateClass();
    Proc p1 = new Proc(d.DisplayDate);

Subsequently we can make p1 encapsulate the DisplayTime method on MyTimeClass as follows:

     p1 = new Proc(MyTimeClass.DisplayTime);

Applying the Delegate Object

We apply the method encapsulated by a delegate by calling the Invoke method on the delegate.

    p1.Invoke();

Note that there is no explicit mention of the application of the object method. The receiver object is frozen inside the delegate at the time of instantiation.

Three actions need to happen as a result of applying a delegate:

  • Look up the value (the current binding) of the delegate (which would be the object method to apply).
  • Look up the values (the current bindings) of the variables passed to the Invoke method itself.
  • Apply the method to those values; i.e., call it with those values as arguments.

In J#, Invoke must be called explicitly in order to apply the delegate. This is somewhat like calling the apply procedure in Scheme [1], which lets you to call any procedure and specify a list of values to be passed as arguments. However, there is a difference. apply takes a procedure and a list of values, and then calls the procedure with those values as arguments. In the use of Invoke there is no explicit mention of the object method being applied.

Delegate and Interfaces

As we have already seen, delegates are similar to single method interfaces, with the difference being that interfaces require a target method's type to have predeclared compatibility with the interface's type. Delegates, on the other hand, can be bound to methods of any type. A method and a delegate type are compatible if both of the following are true:

  • They have the same number of parameters, with the same types, in the same order, and with the same parameter modifiers.
  • They have the same return-types.

As a result, delegates allow a more loosely coupled integration between components.

Additionally, interfaces have a few shortcomings that limit their usefulness:

  • A class can only implement an interface once.
  • Name collisions between interface members sometimes make it impossible for a class to implement multiple interfaces.
  • A class that implements an interface must expose the implemented members publicly.

These shortcomings make it a little difficult to use interfaces for event handling. Delegates, on the other hand, make event handling easy (as we shall soon see).

Multicast Delegates

It is possible to chain together multiple delegates to form a multicast delegate, so that a single call to Invoke can trigger calls to more than one method.

The System.Delegate type supports two methods to manage delegate chains: Combine and Remove.

Consider the following delegate object addressing the DisplayTime method on the MyTimeClass:

Proc p2 = new Proc(MyTimeClass.DisplayTime);

We can now combine the delegates p1 and p2 as follows:

Proc p3 = (Proc) System.Delegate.Combine(p1, p2);

p3 represents a delegate chain. A call to p3.Invoke() triggers calls to the methods encapsulated by p1 and p2 (in that order).

The System.Delegate.Remove method enables removing delegates from a delegate chain.

Note that both of these methods return a new delegate reference, which references the updated delegate chain.

It is also possible to alter the way invocation works on a delegate chain. The GetInvocationList method on the System.Delegate type returns all the delegates in the chain as an array. When you have access to this array, you can decide the order in which to perform the individual invocations.

In the case of a multicast delegate, if the invocation returns a typed value, only the last method's value will get returned to the caller. Also, if any of the methods throw an exception, the invocation will be stopped at that point and the exception will be thrown to the caller.

Implementation Support for Delegates

Recall that the following declares a delegate type.

/** @delegate */
public delegate void Proc();

When the J# compiler sees these two lines of source code, it emits a complete class definition. Here is a typical resulting IL:

.class public auto ansi sealed Proc
       extends [mscorlib]System.MulticastDelegate
{
  .method public hidebysig specialname rtspecialname 
          instance void  .ctor(object 'object',
                               native int 'method') runtime managed
  {
  } // end of method Proc::.ctor
  .method public hidebysig strict virtual 
          instance void  Invoke() runtime managed
  {
  } // end of method Proc::Invoke
  .method public hidebysig newslot strict virtual 
          instance class [mscorlib]System.IAsyncResult 
          BeginInvoke(class [mscorlib]System.AsyncCallback callback,
                      object 'object') runtime managed
  {
  } // end of method Proc::BeginInvoke
  .method public hidebysig newslot strict virtual 
instance void  EndInvoke(
                  class [mscorlib]System.IAsyncResult result) 
                  runtime managed  {
  } // end of method Proc::EndInvoke
} // end of class Proc

The compiler generates a sealed class extending from the CLR provided System.MulticastDelegate class. The compiler uses the signature of the delegate to generate the signatures of the generated class methods. For example, note that the signature of the Invoke method is identical to the signature of the Proc delegate. The only additional methods that a delegate can possess are used for asynchronous calls [5]. In such cases the two additional methods are called BeginInvoke and EndInvoke.

The following line allocates the delegate object and causes the constructor to be invoked:

     p1 = new Proc(MyTimeClass.DisplayTime);

When the constructor is invoked, two arguments are passed to it—the object reference to the type defining the method being delegated, and the integer value of the function pointer to the managed method being delegated. Here is a typical resulting IL:

...
  IL_000b:  ldftn      void MyTimeClass::DisplayTime()
  IL_0011:  newobj     instance void Proc::.ctor(object,
                                                 native int)
...

The following line invokes the delegate:

    p1.Invoke();

In order to invoke the encapsulated function, the pattern of the IL generated is as follows:

    <push reference to delegate object>
    <push arguments to invocation>
    callvirt instance retType DelegateClass::Invoke( <args>)

where retType is the return type of the encapsulated method.

In our example, the instance of the delegate is held in a local variable, and the typical resulting IL is as follows:

...
  IL_0017:  ldloc.0
  IL_0018:  callvirt   instance void Proc::Invoke()
...

Note that there is no mention of the object receiving the encapsulated method. The receiver object is frozen inside the delegate at the time of instantiation.

The user cannot provide the implementation for any of the methods emitted by the compiler, nor does the compiler plant any explicit IL. The implementation for these methods is provided by the runtime as denoted by the runtime-managed [6] annotation on the methods.

Type Safety, Security, and Verifiability

Delegates are entirely opaque structures. There are no operations on delegates except for a constructor, and methods to invoke the encapsulated function. The code of even these methods is supplied by the runtime rather than by the compiler. The runtime is able to guarantee the encapsulated method has the correct signature. Since a delegate encapsulates an object reference, it can rely on the metadata for this object to maintain types-safety at runtime. That is the key to a delegate being typesafe and verifiable.

A delegate can be considered a mechanism to indirectly invoke a method. Such an invocation retains the semantics of the more conventional method invocation, however, in that the method invocation is polymorphic, and it is not possible to bypass any security demands on the actual method that gets invoked. Security is not compromised when indirectly invoking methods through delegates.

Type Equivalence

Delegates support name-equivalence, not structural-equivalence. Specifically, two different delegate types that have the same parameter lists and return type are considered different delegate types.

Continuing with the present example, consider a second delegate:

/** @delegate */
public delegate void Proc2;
Proc2 p2 = new Proc2(MyTimeClass.DisplayTime);
if (p1 == p2) ...  // Compile-time error; different types

The above assignment fails because Proc and Proc2 are considered to be distinct types.

To check if two delegates encapsulate the same methods, we must use the .equals method as below:

if (p1.equals(p2)) ...  // this is Ok

Delegates and Events

The canonical use of delegates is in the support for events [7].

In event communication, a class serves as an event source and "raises" an event. Raising an event causes one or one or more methods (event handlers) to be invoked (notified). Event handlers can dynamically register themselves with the event source to be notified when an event is raised, which means that the class that raises the event (source) does not know a priori which objects or methods need to be notified when an event is raised. What is needed is an intermediary between the source and the sink. Multicast delegates are tailor-made for this purpose. The delegate acts as an event dispatcher for the class that raises the event by maintaining a list of registered event handlers for the event.

Here is an example of programming events with delegates:

/** @delegate */
public delegate void EventHandler();
public class EventSource {
  public void someMethod() {
    // This method raises the event invoking all the handlers
    if (el != null) {
      el.Invoke();
    }
    else {
      System.out.println("No handlers");
    }
  }
  /** @event */
  public void add_MyEvent(EventHandler eh) {
    if (el != null) {
      el = (EventHandler) System.Delegate.Combine(el, eh);
    }
    else {
      el = eh;
    }
  }
  /** @event */
  public void remove_MyEvent(EventHandler eh) {
    el = (EventHandler) System.Delegate.Remove(el, eh);
  }
  private EventHandler el;     
}
public class app {
  public static void EventSink1() {
    System.out.println("In EventSink1");
  }
  public static void EventSink2() {
    System.out.println("In EventSink2");
  }
  public static void main(String [] args) {
    EventSource es = new EventSource();
    EventHandler eh1 = new EventHandler(app.EventSink1);
    es.add_MyEvent(eh1);
    es.someMethod();
    // Add another handler
    EventHandler eh2 = new EventHandler(app.EventSink2);
    es.add_MyEvent(eh2);
    es.someMethod();
    // lets remove the handler we added above
    es.remove_MyEvent(eh2);
    es.someMethod();
    // Remove even the first handler
    es.remove_MyEvent(eh1);
    es.someMethod();
  }
}

Conclusion

Delegates may be thought of as object-oriented "function pointers." A raw function pointer is just a memory address, and does not carry any additional information such as the number of parameters the function expects, the types of these parameters, or the function's return value type. In contrast, delegates are type-safe and verifiable, and may be used for both static and instance methods. Interfaces enable a "tight" coupling between components and require a target method's type to have predeclared compatibility with the interface's type. In contrast, delegates allow for a more loosely coupled integration between components.

By introducing a method reference as a first class type, delegates enable writing higher order methods (i.e., methods that take other methods as parameters) in a simple and elegant manner, and form the basis for events.

References

[1] Dybvig, Kent R., The Scheme Programming Language 2e, Prentice Hall, 1996.

[2] Arnold, K., Gosling, J., The Java Programming Language 2e, Addison Wesley, 1998.

[3] Common Language Infrastructure (CLI) Partition I - Architecture MSDN 2004.

[4] Visual J++ Product Documentation MSDN, 2004.

[5] Asynchronous Delegates, .NET Framework Developer's Guide, MSDN, 2004.

[6] Common Language Infrastructure (CLI) Partition II: Metadata Definition and Semantics MSDN, 2004.

[7] Visual J# Reference, Defining and Using Events Visual J# .NET product documentation, MSDN, 2004.

© 2010 Microsoft Corporation. All rights reserved.   Terms of Use | Trademarks | Privacy Statement
Page view tracker
Rate the Lightweight library
x
Lightweight builds on ScriptFree (loband) by adding features you've requested: a SearchBox and default code language selection.
Do you like the SearchBox?
Do you like the tabbed code blocks?
How useful is this topic?
Tell us more.
Thanks
x
You're helping to improve MSDN Online.
Feedback
Switch View
Classic
Lightweight Beta
ScriptFree
Switch View