C# 2.0 has made delegates even easier to work with, using delegate inference, covariance/contravariance and anonymous methods. All of these changes are useful today, but many will become even more important in C# 3.0. This article assumes a reasonable knowledge of delegates. If you are new to the subject, please read my article on delegates and events first.
This is actually a smaller topic than anonymous methods, but it'll make the topic of
anonymous methods easier to understand, by keeping the code smaller. Basically,
delegate inference lets you create a delegate instance without the new DelegateType
part wherever the compiler can work out which delegate you mean. Here's an example:
using System; delegate void SomeAction(); class Test { static void Main(string[] args) { // Without using delegate inference SomeAction oldStyle = new SomeAction (SayHello); // Now using delegate inference SomeAction newStyle = SayHello; } static void SayHello() { Console.WriteLine ("Hello"); } } |
This can be used with events (e.g. button.Click += ClickHandler;
) and
anonymous methods. Occasionally you will need to tell the compiler
what kind of delegate you're trying to build, in which case the old syntax is the way to go.
In earlier versions of C#, the signature of the method used to implement a delegate had to match the delegate signature exactly, including return type. In C# 2.0, return types can be covariant and parameter types can be contra-variant. Now, these are big words which basically mean "you're allowed to use any method which is guaranteed not to muck things up". To be specific:
Return type covariance means that if a delegate is declared to return a base type, an instance can be created using a method which is declared to return a type derived from it.
Parameter type contra-variance means that if a delegate parameter is declared to be a derived type, an instance can be created using a method which has a base type for that parameter.
The reason this works is that any caller of the delegate must provide arguments matching the delegate's signature (and those arguments will always be compatible with base types of the parameters), and the implementation just has to guarantee that it will return something which can be treated as a value of the declared return type of the delegate.
Here's an example of both covariance and contra-variance:
using System; using System.Text; public class Test { delegate Encoding MyDelegate (string parameter); static UTF8Encoding SomeMethod (object p) { return new UTF8Encoding(); } static void Main() { MyDelegate d = SomeMethod; } } |
Any string
argument which could be passed into the delegate is fine to be treated
as an object
reference by SomeMethod
, and any UTF8Encoding
returned by SomeMethod
can be treated as an Encoding
reference by the
caller.
Delegates are a wonderful idea. They make eventing models simpler, and get away from a lot of single method interfaces (or worse, multiple method interfaces with adapters which do nothing until they're overridden) which afflict Java. Java came up with a way to make it slightly less painful to override a single method than it would be otherwise: anonymous inner classes. These allow you to derive from a class or implement an interface "inline" in the middle of a method. They allow you to use local variables from the method you're running in (with restrictions) and even private variables and methods from the class you're running in. As you're deriving from one class and have implicit access to an instance of another, it's almost as if you get multiple inheritance. Almost. Unfortunately, they look pretty ugly. Here's an example:
// Warning! Java code! Don't get confused by it! interface SomeActionable { void doSomeAction(); } public class Test { public static void main (String[] args) { // Only final (readonly) local variables can be // used in an anonymous inner class final String arg = args[0]; // Anonymous inner class SomeActionable instance = new SomeActionable() { public void doSomeAction() { // Equivalent to Console.WriteLine System.out.println (arg); } }; // Now we've defined our implementation, use it instance.doSomeAction(); } } |
This creates an implementation of the SomeActionable
interface inline - it just prints out the
first command line parameter specified. It's often better than having a whole class just for the sake
of doing something (especially if you have lots of different implementations, each of which is really
just a line of code), but it's ugly to look at and gets worse if you need to distinguish between
accessing the class you're actually deriving from and the class you're declaring the anonymous inner class in.
Now, back to C#. In versions prior to 2.0, you couldn't create a delegate instance unless you had a normal method which had the code you wanted to run. When the delegate only needs to do one thing (e.g. pass on the call to another component, with some different parameters) this can be a bit of a pain - it's often much more readable to see the code you're going to execute at the point you use the delegate, and all those extra methods usually do nothing outside their use for constructing delegates. In addition, if you need to use some context which isn't already in your object within a delegate implementation (e.g. a local variable), you sometimes need to create a whole extra class to encapsulate that context. Yuk. Fortunately C# 2.0 has a delegate equivalent to Java's anonymous inner classes - but without as much mess.
Here's a rough equivalent of the Java code above, this time in C# 2.0:
using System; delegate void SomeAction(); class Test { static void Main(string[] args) { SomeAction instance = delegate { Console.WriteLine(args[0]); }; instance(); } } |
Now, admittedly part of the reason this is so much shorter is the lack of comments and the way I've included the inline code without putting line breaks before/after the braces. However, because there's so little to the delegate definition, I could afford to do that without losing readability.
Similar to Java and anonymous classes, when the C# compiler encounters an anonymous method, it either creates an extra (nested) type or an extra method within the current type behind the scenes to contain the delegate implementation. It's easy to see why an extra method would be required - but why would an extra type be needed? It's all to do with captured variables...
This is where things can become slightly difficult. Like many things, captured variables are fabulously useful, but to start with they can be hard to understand, and they can change some of what you may think you already know about C#. The purpose of them is very simple though - they're there to make local variables from the method declaring an anonymous method available within the delegate itself.
Static and instance variables of the class don't need to be captured - normal instance and static methods can be created by the
compiler to access those. However, look at the example given above. How can the delegate "see" the value of args
to print out args[0]
? In the code above, it's fairly simple - the delegate is only invoked within the method - but
the delegate could be returned from the method, or passed to another method, or stored in a variable somewhere. Just to
make things concrete, here's an example which does just that:
using System; delegate void SomeAction(); class Test { static void Main(string[] args) { SomeAction instance = MakeDelegate(); instance(); instance(); } static SomeAction MakeDelegate() { Random rng = new Random(); return delegate { Console.WriteLine (rng.Next()); }; } } |
Here, a local variable is available in MakeDelegate
, and the delegate uses that local variable
to print a new random number every time it's invoked. But what's the scope of that variable? Does the variable even
exist outside MakeDelegate
? Well, the answer is yes - because the compiler compiles it into an instance
variable in a new type behind the scenes. If you compile the above and look at it with ildasm
, you'll see
that there actually isn't a Random
local variable in MakeDelegate
at all, as far as
the runtime is concerned! Instead, there's a local variable of a nested type with some compiler-generated name (the time I
compiled it, the name was <>c__DisplayClass1
). That type has a Random
instance variable,
and when rng
is assigned or used within MakeDelegate
, it's that variable which is actually
used. The method used to implement the delegate signature is a member of the nested type, so it is able to get at
the Random
instance even after MakeDelegate
has finished executing.
Things become really tricky when there are multiple local variables being used in the delegate, in particular when some of them are effectively created several times. This is best demonstrated with an example:
using System; delegate void SomeAction(); class Test { static void Main(string[] args) { SomeAction[] instances = new SomeAction[10]; int x; for (int i=0; i < 10; i++) { x=0; int y=0; instances[i] = delegate { Console.WriteLine ("{0}, {1}", x, y); x++; y++; }; } instances[0](); // Prints 0, 0 instances[0](); // Prints 1, 1 instances[0](); // Prints 2, 2 instances[1](); // Prints 3, 0 instances[0](); // Prints 4, 3 x=10; instances[2](); // Prints 10, 0 } } |
Don't worry if it takes you a while to understand the output - it certainly threw me! Look carefully at
where x
and y
are declared. There's only one x
variable for the whole method,
but there's a "new" y
variable each time we go through the loop. That means that all the
delegate instances share the same x
variable, but each one has a different y
variable. That's why the first number printed keeps going up, but the second number printed only goes up when the same
delegate instance is called again. (If another instance of the delegate was created inside the loop, then each pair
of delegate instances would share the same y
variable.) Any access to the variable within the main body
of the method uses the "current" set of variables - so any change to y
within the loop itself would
affect just the instance created in that iteration of the loop.
The reason this is contrary to intuition is that usually it doesn't really matter where a variable is declared
within a method (assuming you don't want to use the same name elsewhere in the method, and that you've made the scope
large enough to access it everywhere you want to). The location of the assignments matters, of course, but the actual
declarations have previously only affected the scope of the variable. They now affect the behaviour, as seen above -
the way the values of x
and y
behave in the delegate instances are very different.
In case you're wondering how some variables are shared and some aren't, in the above code two extra types
are created - one with an x
variable, and one with a y
variable and another variable
referring to an instance of the first extra type. The delegate implementation is a member of the second type,
and each instance of the second type has a reference to the same instance of the first type. You can imagine
how complicated things must get when even more variables are involved!
As I said before, captured variables are very useful - but they come at the price of readability. Examples like the code above, where different variables are shared or not shared, should be rare in real code. Anything where the reader has to work out just where a variable came from and which variables will be associated with which delegates is going to be a pain at maintenance time.
yield
statementsBack to the main C# page.