You will learn about the following in this chapter:
What a delegate is
Defining and instantiating delegates
Using delegates as arguments to method calls and creating arrays of delegates
Multicast delegates and why they are useful for implementing event-driven programs
What an event is and how it can be implemented with a delegate
The basic architecture of a typical event-driven program
Recall from Chapter 17, "Inheritance Part II: abstract Functions, Polymorphism, and Interfaces" how inheritance enabled us to reference objects of different subclasses (such as Circle, Rectangle and Triangle) with the same variable of a common ancestor class (such as Shape). Through dynamic binding (by calling DrawYourself in Shape), this allowed us to automatically call the method implementation for the object that the variable was referencing at the moment of the call. We could, in effect, let the same method call invoke different implementations during runtime. This was powerful because we, as programmers, didn't know in advance which particular subclass objects the ancestor type variable (Shape in our case) would reference during runtime (we didn't know the particular shapes a drawing would contain). So we were able to postpone this decision until runtime.
Delegates, like inheritance, interfaces, and polymorphism, also help us to postpone decisions concerning method implementations until runtime.
A delegate is similar to an abstract method in that it specifies the return type and formal parameter types of a method but not the implementation. The same delegate can, like an abstract method, represent different method implementations during runtime, as long as these methods have the same return type and formal parameters as those specified for the delegate. During runtime, a suitable method is assigned to the delegate, which then encapsulates this method. When the delegate is called, it will delegate the actual execution to the method it encapsulates.
Delegates are useful when we, at the time of writing our source code, only know that an action (along with its return type and formal parameters) must happen at a particular place, but don't know its implementation. This computational problem is often encountered in modern computer programs. One example is event-driven, graphical user interfaces (GUI). As mentioned in Chapter 12, "Class Anatomy Part I: Static Class Members and Method Adventures" the next action in an event-driven program depends on the next event fired in the program. An event could be a mouse click on a button or a key pressed on the keyboard. Often, we know that a button must perform some kind of action when it is clicked, but we don't know the implementation before compile time. If we let the button click call a delegate, we can postpone the decision of which particular method this button click is invoking until runtime. At runtime, we can assign the particular method to the delegate, which then will be called every time the button is clicked. Events and delegates are closely related, as you will see later in this chapter. Both these concepts are pivotal for implementing event-driven programs in C#.