Getting started with delegates–part I
If you’re an experienced Windows developer, then you know that Windows has always relied on callback methods/functions for doing several things (ex.: window procedures). In unmanaged code, this was typically achieved through the use of callback functions which, in practice, referenced only a memory address that “had” some code which should be executed when something special happens.
As you probably know, .NET also supports this concept but, unlike unmanaged code, it does it in a type-safe way. As I said, in unmanaged code callbacks reference only a memory position and there’s no extra information about the callback method (for instance, there’s no info about eventual parameters that should be exposed by an eventual callback method). Since .NET tries to enforce type safety, the callback idea couldn’t just be directly ported from unmanaged code: it needed to be improved so that type safety is correctly enforced. And that’s how we got delegates. In .NET, delegates are always created through the use of the delegate keyword. Let’s take a quick look at a simple example:
In the previous snippet, I’ve started by introducing a new delegate (named Logger). As you can see, it’s compatible with methods that return void and expect a String. Then I’ve added a new class which uses the delegate so that it doesn’t rely in a specific class for outputting information about a number (yes, we could also have used interfaces, but lets use delegates this time). By using the delegate, we can now output information to different places by just using a different delegate instance (for example, we can use one for outputting to the console, other for outputting to a file, etc.). Another thing you’ve probably noticed is that delegates are used as methods, ie, we’re using the Logger delegate as if it’s a method from within our class. The next snippet shows how to instantiate a new delegate which outputs info to the Console:
As you can see, we start by adding a new method which is compatible with the delegate’s signature (notice that ConsoleLogger returns void and expects a string). As we’ll see in the future, we can use static or instance methods (in this case, I’ve opted for a simple static method). You’ve probably noticed that the delegate is being instantiated in a similar fashion to a class. And that’s because it is (but we’ll leave this for a future post). Even though the previous snippet didn’t illustrate it, the truth is that the C# allows for covariance and contra-variance of reference types when binding a method to a delegate. Notice that *reference type* is important here…
Btw, it’s really simple to use delegates to call instance methods, as you can see from the next snippet:
As you can see, the code is really similar to the one we presented earlier. Even though there are several interesting differences in the way both calls are handled internally, there aren’t any from the perspective of the programmer that is using a delegate to call instance or static methods.
Oh, yes, you’ve probably noticed one important difference (when compared with the static method call): I’ve simplified the syntax used for creating the delegate instance. As you can see, I’ve removed the redundant constructor call since the compiler is smart enough for doing that work for me. Btw, and since we’re talking about code simplification, you should also keep in mind that we don’t really need to use named methods and can rely on anonymous methods:
In the previous snippet, we’ve resorted to a lambda expression for creating a method that will be called by the delegate. When the compiler finds this lambda expression, it will automatically translate it into a private static member of that class (notice that the lambda expression doesn’t access any class member!) and replace the previous snippet with a delegate instantiation expression. If our lambda expression referenced any class instance member, then we’d end up with an instance private method.
And I’d say that’s all for now. In the next post, we’ll keep looking at delegates and we’ll see what happens when we instantiate