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);
|
Related Reading
|
In C#, a method is a first-class object in the same way a Class
like String.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 a
Class 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 like RuntimeExceptions).
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 the build 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 a Runnable. 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 a Delegator 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 from buildRunnable. The cast is performed
in the method implementation.
The critical code is in the method build. This method
constructs a DelegateProxy, exposed through an
Delegate 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,
the DelegateProxy 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.
|
Related Reading
|
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 Runnables 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.
Return to ONJava.com.
Copyright © 2007 O'Reilly Media, Inc.