A delegate is a special type that maintains a reference to a method. After a delegate has been assigned to an actual method that matches the signature defined by the delegate itself, the delegate can be used the same way the real method can be used. You can execute the method by executing the delegate, pass parameters, and so on.
As long as the method's signature matches the signature defined by the delegate, the method and associated parameters can be assigned to the delegate. This allows you to programmatically change methods and add additional methods to existing classes.
Delegates can also be passed as parameters, making them perfect for callback solutions. A callback is where a method is passed a delegate parameter indicating a method that should be called back periodically. For example, you could have a long-running method invoke the callback method periodically to update a progress bar.
Delegate declarations look much like a method signature, preceded by the keyword delegate, as shown in the following examples:
delegate void MessagePrintDelegate(string msg); delegate int GetCountDelegate(object obj1, object obj2); delegate void LongRunningDelegate(MessagePrintDelegate mpCallBack);
A delegate can have scope just like any other member variable; however, you cannot apply the static keyword to a delegate. As such, you can rewrite the preceding declarations as shown in the following example:
public delegate void MessagePrintDelegate(string msg); private delegate int GetCountDelegate(object obj1, object obj2); internal delegate void LongRunningDelegate(MessagePrintDelegate mpCallBack);
When you have a delegate defined, you can create an instance of the delegate, assign a method to it (as long as the signature matches), and pass it as a parameter to other methods. The code in Listing 9.1 illustrates some basic techniques for working with delegates.
Naming Conventions for Delegates
When creating delegates, it is standard convention to postfix the name of the delegate with the word "Delegate" (capitalization is intentional). For example, a delegate that prints a message should be called PrintMessageDelegate. Because delegates are so syntactically similar to regular data types, the use of the postfix is designed to make reading and maintaining event-based code easier.
Listing 9.1. Basic Delegate Manipulation
When you run the preceding code, you get the output shown in Figure 9.1.
Figure 9.1. Output of simple delegate demo.
Covariance and Contravariance
Covariance and contravariance might seem like pretty complex words, but they represent some fairly easy-to-understand concepts. Covariance refers to the ability of a delegate method to return derivative data types. In the preceding example, you'll notice that the Contact class derives from Person. Suppose that you have a delegate defined as follows:
delegate Person GetPersonDelegate();
Then covariance allows for the following lines of code to work properly:
GetPersonDelegate gpd = new GetPersonDelegate(GetPerson); GetPersonDelegate gpd2 = new GetPersonDelegate(GetContact);
This sample shows that covariance allows you to create an instance of the GetPersonDelegate for a method that returns a Person instance as well as a method that returns a Contact instance.
Contravariance refers to the ability of a delegate method to take derivative classes as parameters. In Listing 9.1 you saw the delegate GetCountDelegate, which takes two parameters of type Person. Contravariance allows you to pass parameters of derivative types to delegate methods, as shown in the following example:
Contact c = new Contact(); Person p = new Person(); int x = gcd(p, p); int y = gcd(c, c);