While C# has a set of capabilities similar to Java, it has added several new and interesting features. Delegation is the ability to treat a method as a first-class object. A C# delegate is used where Java developers would use an interface with a single method. In this article, the use of delegates in C# is discussed, and code is presented for a Java Delegate
object that can perform a similar function. Download the source code here.
C#, pronounced C Sharp, is a language developed by Microsoft as a part of its .NET initiative. The language is extremely similar to Java. Were it not for legal difficulties between Microsoft and Sun, there is little question that Microsoft would have chosen Java to fill the role in its plans that is currently held by C#. The major features C# has in common with Java include garbage collection, a virtual machine, single inheritance, interfaces, packages (called namespaces), and the fact that all code is encapsulated within objects.
There are also a few significant differences between the two languages. Because the developers of C# had the advantage of carefully examining Java while developing their language, it is not surprising that some of the differences attempt to address significant problems that are difficult to deal with in Java. This article focuses on one item that Microsoft added to C#, why it was added, and how similar functionality could be implemented in Java.
C# introduces the concept of delegates, which represent methods that are callable without knowledge of the target object. Consider these situations:
public class Class1 {
...
public void show(String s) { .. }
}
public class Class2 {
...
public void show(String s) { .. }
}
Here, two classes share a common method, show
, performing a similar function, display of data. We would like to be able to call that method in the same way for Class1
and Class2
. If the two classes share a common interface, we can simply treat instances of either class as instances of that interface. Unfortunately, if the classes do not share an interface, as in the above example, there is no easy way to make a uniform call. If the developer controls the code for both classes, it is possible to retrofit a common interface. When one or both classes are in library code, there is no easy fix.
A more complex situation is illustrated in the example below:
public class Class1 {
...
public void show(String s) { .. }
}
public class Class2 {
...
public void display(String s) { .. }
}
These two classes have the methods show
and display
, which perform a similar function and have a similar signature. That is, they take similar arguments, return similar data, and could be used in a loop doing conceptually similar things. However, because the names of the two methods are different, no interface will recognize the two as performing the same action.
Java developers may address these issues through reflection (by generating an interface and implementing it with inner wrapper classes) or by constructing a dynamic proxy. All of these solutions are somewhat clumsy and require non-trivial amounts of code.
Consider how C# would address this issue. In the sample below, we define three classes that implement similar methods, two with different names and a third with a static implementing method:
// define three classes with similar methods
// two instances with differing names, one static.
public class Class1 {
public void show(String s) { Console.WriteLine(s); }
}
public class Class2 {
public void display(String s) { Console.WriteLine(s); }
}
// allows static method as well
public class Class3 {
public static void staticDisplay(String s) { Console.WriteLine(s); }
}
We will now define a new data type, doShow
, which abstracts the common features of the methods in all three classes. That is, they all take a single string as an argument and return void
. This is done using the C# delegate
keyword.
public delegate void doShow(String s);
Think of a delegate as an interface declaring exactly one method. An instantiation of a delegate is similar to an anonymous inner class that implements the interface through a one-line call to a single method (static or instance) with a compatible signature.
With this new data type in hand, we may now arrange to invoke all three methods via a common abstraction:
public class TestDelegate
{
// define a datatype as a method taking a string returning void
public delegate void doShow(String s);
public static void Main(string[] args)
{
// make an array of these methods
doShow[] items = new doShow[3];
items[0] = new doShow(new Class1().show);
items[1] = new doShow(new Class2().display);
items[2] = new doShow(Class3.staticDisplay);
// call all items the same way
for(int i = 0; i < items.Length; i++) {
items[i]("Hello World");
}
}
}
The main function creates an array populated with newly instantiated doShow
objects. These refer to the various instance and class methods defined above. Let's take a closer look at the instantiation of a delegate:
items[1] = new doShow(new Class2().display);
In C#, a method is a first-class object in the same way a Class
likeString.class
is a first-class object in Java. In C#, if we reference a method on an object (omitting the parentheses that would normally signal a method call), C# instead treats the method name like a field, returning the object representing that method. The constructor of a C# delegate expects to be called with just such a method object.
In Java terms, C# is dynamically creating an interface that declares a single method. When one considers how many interfaces (especially listeners and other event handlers) fit this description, the utility of this approach is apparent. C# uses delegates rather than Listener
interfaces to implement most of its event handling. Threads in C# are constructed with delegates (System.Threading.ThreadStart
), unlike Java, which uses the Runnable
interface.
When an instance of a delegate is constructed, the actions the compiler takes are similar to the Java equivalent of building a wrapper class. This wrapper class exposes the interface defined by the declaration of the delegate, implementing the interface by calling the method that was passed to the delegate constructor.
The C# approach requires hooks into the compiler not available in Java. These hooks allow methods to be accessed at compile time via a convenient syntax in much the same way we would write String.class
to access a known class at compile time. This approach allows the C# compiler to perform type checking on the arguments to a delegate invocation when compiling it, rather than throwing a type error at runtime
The code presented in this article implements a significant portion of the functionality of C# delegates in Java. Two ways of accomplishing this will be presented. In the first case, the developer describes the method to be delegated by providing a list of the parameter and return classes. In the second case, the parameters are deduced by examining a suitable interface that declares a single method. The code presents a factory class, Delegator
, capable of handling either case. The factory method, build
, returns an object implementing the Delegate
interface. Where the Delegator
has been constructed with an interface, the return is a Proxy
implementing that interface.
Delegator
ClassTwo methods may be considered comparable if the argument lists of each method are assignable the same common list of classes and if the return types are assignable to a common type. As an example, if Foo
is a superclass of Bar
, the two methods
public String m1(Foo p1);
public Object m2(Bar p1);
both match a signature taking Bar
and returning Object
. We may express this signature in code by providing aClass
object that represents the return type and an array of Class
to represent the parameter types. We may also express this signature by providing an interface with a single method to be used as an exemplar.
A method may match the signature described by a Delegator
in the weak sense that all arguments are assignable to the declared types rather than the stronger test required by a Java interface that the arguments be identical. Also note that methods are considered comparable even it they throw different exceptions. Delegation will convert all exceptions into a runtime DelegateInvokeException
emulating C# behavior (all C# exceptions act likeRuntimeExceptions
).
Delegator
provides a factory method, build
, which associates the method template with a specific implementation. The implementation is a combination of either an instance method and a target instance or a static method. In either case, the method must be compatible with the requested signature. The object returned by thebuild
method will satisfy the Delegate
interface, which contains the method:
public Object invoke(Object[] args);
The returned object is a thin wrapper that invokes the method on the supplied object, converting any checked exceptions returned by the wrapped object into a runtime DelegateInvokeException
. The code in Scenario 1 shows use of this basic type of Delegate
object.
Scenario 1. Using a generic Delegate
object
class Class1 {
public void show(String s) { System.out.println(s); }
}
class Class2 {
public void display(String s) { System.out.println(s); }
}
// allows static method as well
class Class3 {
public static void staticDisplay(String s) { System.out.println(s); }
}
public class TestDelegate {
public static final Class[] OUTPUT_ARGS = { String.class };
public final Delegator DO_SHOW = new Delegator(OUTPUT_ARGS,Void.TYPE);
public void main(String[] args) {
Delegate[] items = new Delegate[3];
items[0] = DO_SHOW .build(new Class1(),"show,);
items[1] = DO_SHOW.build (new Class2(),"display");
items[2] = DO_SHOW.build(Class3.class, "staticDisplay");
for(int i = 0; i < items.length; i++) {
items[i].invoke("Hello World");
}
}
}
The code described above offers many of the advantages of C# delegates. Methods, either static or dynamic, can be treated in a uniform manner. The complexity in calling methods through reflection is reduced and the code is reusable, in the sense of requiring no additional classes in the user code. Note we are calling an alternate convenience version of invoke
, where a method with one parameter can be called without creating an object array.
Scenario 2. Delegating via an interface
One advantage of C# delegates that is still missing is static type checking enforced by the compiler. In the example above, it is possible to call invoke
on a returned Delegate
with a Date
object, and the error will not be discovered until run time. In order to get the full benefits of compiler support, it is necessary to construct the Delegator
with an interface. The interface may be well-known or special-purpose, but should declare a single method. The signature of that method becomes the template used by the Delegator
. In addition, the returned object will be a proxy that implements the requested interface.
// interface to implement
public static interface IStringDisplay {
public void doDisplay(String s);
}
public final Delegator ST_DEL = new Delegator(IStringDisplay.class);
public void testDelegate()
{
IStringDisplay[] items = new IStringDisplay[3];
// build the delegates
items[0] = (IStringDisplay) ST_DEL.build(new Class1(),"show");
items[1] = (IStringDisplay) ST_DEL.build(new Class1()2,"display");
items[2] = (IStringDisplay) ST_DEL.build(Class3.class,"staticDisplay");
// call the delegates
for(int i = 0; i < items.length; i++) {
items[i].doDisplay("test");
}
}
Note that while a cast is required to convert the value returned from the build
method into an instance of the desired interface, invocations of the delegated method are now made through the interface with full static type checking.
One major use of delegates in C# is in threading. Rather than constructing a thread with an instance of Runnable
, threads in C# are constructed by using a delegate, Thread.ThreadStart
, with the appropriate signature. In Java, a similar problem exists where a developer wants to call a no-argument method as the active portion of aRunnable
. While this may be accomplished with an anonymous inner class, the construct is clumsy and reduces the readability of the code.
This important usage pattern is supported by the Delegator
class, which implements convenience methods to create delegates implementing Runnable
. This is done by providing a static, final instance variable holding aDelegator
constructed using the well-known interface Runnable
, implementing two static methods that build delegates using this Delegator
and cast the returned object to the underlying interface. Similar code could be used any time it is necessary to build many delegates that all implement a particular interface.
static final Delegator RUNNABLE_DELEGATE = new Delegator(Runnable.class);
public static Runnable buildRunnable(Object o,String methodName) {
return((Runnable)RUNNABLE_DELEGATE.build(o,methodName));
}
public static Runnable buildRunnable(Class c,String methodName) {
return((Runnable)RUNNABLE_DELEGATE.build(c,methodName));
}
The above code can be called with a line such as:
Runnable r = Delegator. buildRunnable(this,methodName);
Note that because a special-purpose method has been used, there is no need to cast the return value frombuildRunnable
. The cast is performed in the method implementation.
The critical code is in the method build
. This method constructs a DelegateProxy
, exposed through anDelegate
interface that is a wrapper around the method named in the call to build
. If the Delegator
was constructed by specifying an interface, the returned object is wrapped in a dynamic Proxy
so that it will appear to the Java runtime as an instance of the requested interface.
/**
* @param target non-null target with a bindable method
* @param MethodName name of the method
* @return non-null IDelegate. If getInterface() is non-null the returned
* Delegate will be a dynamic proxy implementing that interface
*/
public Delegate build(Object target,String methodName)
{
Class myInterface = getInterface();
DelegateProxy theDelegate = new DelegateProxy(target,methodName,this);
// build a dynamic proxy
if(myInterface != null) {
Class[] interfaces = { myInterface,Delegate.class };
Delegate ret = (Delegate)java.lang.reflect.Proxy.newProxyInstance(
target.getClass().getClassLoader(),
interfaces, theDelegate);
return(ret);
}
return theDelegate;
}
The constructor DelegateProxy(target,methodName,this)
uses reflection to find the best method in the target class matching the signature contained in the Delegator
. When an interface has been specified, theDelegateProxy
can be used as an InvocationHandler
to construct a Proxy
implementing the specified interface. The returned object may then be called using the Delegate
's invoke
method or, if an interface is specified in the Delegator
, cast to that interface and used as an instance of that interface.
Sufficient information to build delegates is present once classes are loaded. Binding a delegate is a non-trivial operation requiring identification of an appropriate method in the target class. When a delegate is constructed with an instance method, the build
call can occur any time after the target instance has been created. Delegates invoking static methods can be constructed at load time. It is usually a good idea to build delegatees as early as possible, caching them for later use. Actually performing method calls through delegates is relatively cheap.
The most logical use of delegates involves messaging and event handling where the code is infrequently executed; i.e., not in a tight loop. In these situations, executing the code contained in the target method usually takes significantly longer than the process of finding and invoking the method. In addition, Hotspot and JDK 1.4 have significantly reduced the cost of method calls. I found that executing the loop in TestDelegate
(three calls) for 10,000,000 iterations took 43 seconds on a 1.5GHz Athlon processor running JDK 1.4 under Windows 2000. This averages slightly more than one microsecond per call. This cost can be ignored in all but the tightest loops.
This approach represents an implementation of the Adapter pattern (Gamma et al, Design Patterns). It differs from a Proxy in that an Adapter maps a number of different methods into identical calls, allowing multiple objects implementing methods with different names but compatible signatures to be used interchangeably. It will also work with or without a target interface to implement. The Delegator
class simplifies and generalizes the steps in creating an Adapter. Delegates are simple to use, and a single delegate instance may be reused multiple times.
Where an interface is known or can be constructed, use of delegates provides a simple, readable way to coerce a method into implementing the actions in that interface. Common interfaces, such as Runnable
and many event listeners, may easily be mapped to any matching public method. In these cases, the Java code is almost as simple as the code that could be written using C#'s built-in delegate construct.
In my own development, I find that delegates implementing interfaces are more useful than those using invoke
. The buildRunnable
method is especially useful. In Swing programming, where large numbers of Runnables
are needed to pass control to the swing thread, the ability to turn methods into Runnable
s is particularly useful. Delegates allow me to largely eliminate the need for anonymous inner classes, improving the readability of my code.
Design Patterns: Elements of Reusable Object-Oriented Software by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides. Addison Wesley Professional Computing Series, 1994.
Steven M. Lewis , PhD, is a Director of Development for UnifiedSignal, a provider of telecom solutions.
Wilhelm Fitzpatrick is an independent consultant specializing in Java development for enterprise platforms