What Is a Delegate?


The Common Type System provides the .NET Framework with a standardized way of describing and manipulating data types in a language-agnostic manner. In addition to common types such as integers, strings, dates, bytes and more, the CTS also defines a delegate, making delegates available to C#, Visual Basic .NET, and any other .NET language.

All delegates in the .NET Framework are descendants of the System.Delegate class. A delegate serves as a placeholder for information about a specific method call. As mentioned earlier, a delegate also is a type-safe, managed function pointer.

The following is a list of the steps required to implement delegates in your application. The first step is the declaration of the delegate. You should use the predefined C# keyword delegate. The following is an example of declaring a delegate:

 delegate string SomeDelegate(string someStringValue); 

This code defines only the new class named SomeDelegate. This class declaration defines the method signature for which the delegate will be used. The signature of a method, with respect to declaring delegates, includes the method's return type and its argument list.

TIP

Remember that the delegate is really just a placeholder for an actual method, not a method itself. As such, the name of the delegate can be anything, and doesn't have to match the name of any real method. A recommended guideline is to postfix the name of the delegate with the word Delegate.


In this case, the method described by the SomeDelegate delegate should return a string value and take one string input parameter (someStringValue).

Because a delegate is a class, it can be declared in both the namespace and in some other class. So, both of the code examples in Listing 10.1 are correct.

Listing 10.1. Declaration of Delegates
 //Example #1 using System;         delegate string SomeDelegate(string someStringValue);         class SomeApplication {                 public static void Main() {                 ...                 } } //Example #2 using System; class SomeApplication {         delegate string SomeDelegate (string someStringValue);         public static void Main() {                 ...         } } 

The second example in Listing 10.1 declares the delegate SomeDelegate as a subclass of the other class (SomeApplication).

NOTE

In the delegate's declaration (before the keyword delegate), you could use an access modifier (private, public, and so on) in the same way that you can for any other class declaration.


After defining the delegate, the second step is the declaration of a variable that is of the delegate's type. An instance of a delegate works just like any other managed object, so its creation is similar to the creation of any other class:

 SomeDelegate someDelegate; 

The preceding code declared the someDelegate variable of the type SomeDelegate.

The third step is initialization of the delegate's instance. Initialization of the delegate's instance can be performed in the same way as the initialization of an ordinary class; that is, by using the new operator with the delegate's constructor:

 SomeDelegate someDelegate; someDelegate = new SomeDelegate([Some Method Name]); 

Let's discuss the second line of the preceding example. As you can see, we've passed an identifier of the method ([Some Method Name]) to the delegate's constructor. So, our delegate's instance will be some kind of pointer to the passed method. The signature of the passed method should be the same as the signature declared by the delegate. In our case, we should pass to the delegate's constructor method with one string input parameter. Also, this method should return a string value.

NOTE

If you are trying to pass a method's identifier to the constructor of an ordinary class, the compiler will return an error of the following format:

 [SomeFile.cs(10:10)]: error CS0654: Method "Delegates.SomeApplication.SomeMethod(string)" referenced without parentheses 

The error can be explained in this way: The concept of delegates is supported at the C# syntax level, and this enables us to pass to the delegate's constructors only an identifier for the method (without parameters). But for nondelegate classes, this feature is unavailable.


An instance of the Delegate class can be bound both to static and nonstatic methods. When you're trying to bind a Delegate with a static method, use the class's name before the method's name. If the name of a class is missing, the compiler uses the name of the class in which the Delegate is initialized. When you're trying to bind a Delegate with a nonstatic method, you use an instance of a class as the identifier. If the name of an object is missing, the compiler uses the current instance (this):

 someDelegate =   new SomeDelegate(SomeClassName.SomeStaticMethodName);//static method someDelegate =   new SomeDelegate(someInstance.SomeNonStaticMethodName);//non static method 

The final step in the process of using delegates is an invocation of the method to which the delegate is bound. You can perform this operation by using the name of the delegate's instance with parameters (it looks like an invocation of an ordinary method). For an example, examine the following sample source code:

 string someResult = someDelegate("Some string value"); 

We've passed a set of input parameters in parentheses.

Listing 10.2 illustrates the simple use of delegates.

Listing 10.2. Sample Source Code Using Delegates

[View full width]

 using System; namespace Delegates {     delegate string SomeDelegate (string someStringValue);     delegate void AnotherDelegate ();     class SomeClass {                     public void SomeMethod () {                             Console.WriteLine("SomeClass.SomeMethod non static method has  been invoked ...");                     }     }     public class SomeApplication {             private static string SomeMethod (string someStringValue) {                     Console.WriteLine("SomeApplication.SomeMethod static method has been invoked with " +                    " parameter [" + someStringValue + "] ...");                     return "[" + someStringValue + "] was passed to SomeMethod ...";             }             [STAThread]             static void Main(string[] args) {                    Console.WriteLine("Invocation of the non static SomeClass.SomeMethod  method through "+                "AnotherDelegate example [in]");                     SomeClass someClass = new SomeClass();                     AnotherDelegate anotherDelegate = new  AnotherDelegate(someClass.SomeMethod);                    anotherDelegate();                     Console.WriteLine("Invocation of the non static SomeClass.SomeMethod method through" +            "AnotherDelegate example [out]");                     Console.WriteLine();                     Console.WriteLine("Invocation of the static SomeApplication.SomeMethod method "+              "SomeDelegate example [in]");                     SomeDelegate someDelegate = new SomeDelegate(SomeMethod);                     string someResult = someDelegate("Some string value");                     Console.WriteLine(someResult);                     Console.WriteLine("Invocation of the static SomeApplication. SomeMethod method through+      "SomeDelegate example [out]");             }     } } 

Listing 10.3 presents the next trace.

Listing 10.3. Trace of Delegates Using Sample Code
 Invocation of the non static SomeClass.SomeMethod method through    AnotherDelegate example [in] SomeClass.SomeMethod non static method has been invoked ... Invocation of the non static SomeClass.SomeMethod method through   AnotherDelegate example [out] Invocation of the static SomeApplication.SomeMethod method through    SomeDelegate example [in] SomeApplication.SomeMethod static method has been invoked with    parameter [Some string value] . [Some string value] was passed to SomeMethod ... Invocation of the static SomeApplication.SomeMethod method through    SomeDelegate example [out] 

Now we can begin a review of the types of delegates.

Types of Delegates

There are two types of delegates in .NET: singlecast and multicast. An example of using a singlecast delegate was reviewed earlier. With the help of this type of delegate, only one method can be invoked per one delegate's invocation. A singlecast delegate (shown in the preceding code ) is a one-to-one ratio between delegate and referenced method. A multicast delegate enables us to invoke either one or several methods simultaneously.

The main difference between a singlecast and a multicast delegate is in the delegate's signature. A delegate that does not return any data (return type void) is considered multicast. Such a delegate can be used to invoke multiple methods with the same signature. You might recognize this model as the event publisher/subscriber model from Windows and event-based programming.

You are allowed to create multicast delegates by inheriting your custom delegate's class from System.MulticastDelegate.

NOTE

If you create a delegate with a return type that is not equal to void, your delegate will be singlecast.


In Listing 10.2, AnotherDelegate was declared as multicast delegate. In Listing 10.4, we modify the previous example by adding one method, AnotherMethod, to SomeClass with a signature that corresponds to the signature of the AnotherDelegate delegate. Also change the invocation format of AnotherDelegate by using a combination of delegates.

Listing 10.4. Sample Source Code of Using Singlecast and Multicast Delegates

[View full width]

 using System; namespace Delegates {         delegate string SomeDelegate (string someStringValue);         delegate void AnotherDelegate ();         class SomeClass {             public void SomeMethod () {                     Console.WriteLine("SomeClass.SomeMethod non static method has been  invoked ...");             }             public void AnotherMethod () {                     Console.WriteLine("SomeClass.AnotherMethod non static method has been invoked ...");             }     }     public class SomeApplication {             private static string SomeMethod (string someStringValue) {                   Console.WriteLine("SomeApplication.SomeMethod static method has been invoked with "+  "parameter [" + someStringValue + "] ...");                   return "[" + someStringValue + "] was passed to SomeMethod ...";             }             [STAThread]             static void Main(string[] args) {                     Console.WriteLine("Invocation of the non static SomeClass.SomeMethod method through AnotherDelegate example [in]");                     SomeClass someClass = new SomeClass();                     AnotherDelegate anotherDelegate1 =   new AnotherDelegate(someClass.SomeMethod);                     AnotherDelegate anotherDelegate2 =   new AnotherDelegate(someClass.AnotherMethod);                     AnotherDelegate   anotherDelegatesCombination =   (AnotherDelegate) Delegate.Combine(anotherDelegate1, anotherDelegate2);                     anotherDelegatesCombination();                     Console.WriteLine("Invocation of the non static SomeClass. SomeMethod and"+   "SomeClass.AnotherMethod methods through AnotherDelegate Combination example [out]");                     Console.WriteLine();                    Console.WriteLine("Invocation of the static  SomeApplication.SomeMethod method "+ "through SomeDelegate example [in]");                    SomeDelegate someDelegate = new SomeDelegate(SomeMethod);                    string someResult = someDelegate("Some string value");                     Console.WriteLine(someResult);                     Console.WriteLine("Invocation of the static SomeApplication. SomeMethod method "+   "SomeDelegate example [out]");             }     } } 

The code in Listing 10.5 is returned by the next trace.

Listing 10.5. Trace of Singlecast and Multicast Delegates Using Code
 Invocation of the non static SomeClass.SomeMethod method through    AnotherDelegate example [in] SomeClass.SomeMethod non static method has been invoked ... SomeClass.AnotherMethod non static method has been invoked ... Invocation of the non static SomeClass.SomeMethod and    SomeClass.AnotherMethod methods through AnotherDelegate Combination example [out] Invocation of the static SomeApplication.SomeMethod    method through SomeDelegate example [in] SomeApplication.SomeMethod static method has been invoked    with parameter [Some string value] ... [Some string value] was passed to SomeMethod ... Invocation of the static SomeApplication.SomeMethod method   through SomeDelegate example [out] 

A more detailed use of multicast delegates will be discussed later.

Delegates Inside

So far, you have seen the simplest example of using delegates. Now you will look deeper into delegates. Let's start from singlecast delegates because multicast delegates are extensions of these.

The following is a declaration of both the Delegate and MulticastDelegate classes:

 public abstract class Delegate : ICloneable, Iserializable public abstract class MulticastDelegate : Delegate 

Both of these are abstract classes. Their implementations are located in the library mscorlib.dll (namespace System).

As mentioned earlier, all singlecast delegates should be inherited from the System.Delegate class. You can find a detailed specification of it on the MSDN site. At the moment, we are interested in only two properties of this class:

 public object Target {get;} public MethodInfo Method {get;} 

If a delegate is bound to a nonstatic method, the Target property will be set as a pointer to the concrete instance of some classthe object's context. If the delegate is associated with a static method, the value of the Target property will be null. The second property (Method) contains all data related to the delegate-associated method. This value can't be null. Both these properties are read-only. So, the values of these properties are set during the process of the delegate's initialization.

Let's review the Delegate's constructor. The class System.Delegate has two constructors:

 protected Delegate(Type target, string method); protected Delegate(object target, string method); 

The first constructor is used for initialization of delegates that are associated with a static method. The first parameter defines the type (class) where a static method is located. The second parameter contains the name of the method for association with the delegate.

The second constructor initializes delegates, which should be bound with a nonstatic method. The first parameter defines the object's context where the bound method is located. The purpose of the second parameter is the same as in the first delegate's constructor.

However, you will use neither the first nor second constructor because C# has language shortcuts that hide some of the underlying plumbing required to implement delegates. The actual usage of delegate declaration and instantiation varies slightly from the .NET constructors.

Now we will discuss how a delegate's associated method could be invoked. Let's discuss code mentioned in the earlier examples.

 string someResult = someDelegate("Some string value"); 

The compiler will recognize that someDelegate is an instance inherited from the System.Delegate class, and will pass all input parameters to the method indicated by the delegate. After this, the compiler will try to invoke the method to which the delegate is bound.

Invocation of the method is performed with help of Reflection. Reflection is a toolkit for working with types at runtime. Reflection enables us to read information about some type, invoke methods, and take the values of properties and many others (see Chapter 11, "Reflection and Code Attributes").

As mentioned earlier, each delegate has the property Method. The type of this property is MethodInfo. This contains the Invoke method, which enables us to perform some method at runtime. The compiler uses data from the Target property of the delegate and the Method property to prepare code for performing methods associated with the delegate.

As input parameters, this method takes the parameter passed to the delegate. In our case, there is only one string parameter, someStringValue, with the value "Some string value".

Combined Delegates

In Listing 10.4, the code declared the AnotherDelegate delegate with a returned type of void. A delegate that has void as its returned type is called a multicast delegate. The main feature of multicast delegates is the possibility to call several methods simultaneously. You could do so by using the static method Combine of the System.Delegate class:

 public static Delegate Combine(Delegate[]) public static Delegate Combine(Delegate, Delegate) 

Let's discuss how to use this method. The following source code lines are from Listing 10.4:

 AnotherDelegate anotherDelegate1 = new AnotherDelegate(someClass.SomeMethod); AnotherDelegate anotherDelegate2 = new AnotherDelegate(someClass.AnotherMethod); AnotherDelegate anotherDelegatesCombination =   (AnotherDelegate) Delegate.Combine(anotherDelegate1, anotherDelegate2); anotherDelegatesCombination(); 

In this code, we've created two multicast delegates of type AnotherDelegate. Each instance of AnotherDelegate in our example is bound to its own method, which has the same signature as the signature of AnotherDelegate.

After creating the delegates, we create the anotherDelegatesCombination instance of the AnotherDelegate class. We perform this operation by an invocation of the Combine method of the System.Delegate class. This method takes collections of delegates, or two delegates, as input data and returns a combination of delegates of the same type. (It returns an instance of the first delegate from the list of delegates and links it with the next delegate in the list.) When combined, the delegate is invoked and all delegates included in the combination also will be invoked.

Let's discuss how the multicast delegate is implemented. Actually, it is very similar to the singlecast delegate. The main difference is this: The class System.MulticastDelegate contains the private field _prev that is a pointer to the other delegate in the combination. With the help of this field, delegates can be linked in the combination.

There are several other useful methods in the System.MulticastDelegate class:

 public static Delegate Remove(Delegate source, Delegate value); 

This method removes the delegate described in the parameter value from the delegates' combination described in the parameter source.

The second method is GetInvocationList:

 public virtual Delegate[] GetInvocationList(); 

This line returns an array of singlecast delegates. If the method is invoked for a multicast delegate, all elements from the delegates list will be returned in an array. In the case of a singlecast delegate, the array will include only one delegate.

Events

The official definition of an event is the following:

An event in C# is a way for a class to provide notifications to clients of that class when some interesting thing happens to an object. The most familiar use for events is in graphical user interfaces; typically, the classes that represent controls in the interface have events that are fired when the user does something to the control (for example, click a button).

Declaring an event is directly dependent on delegates. A delegate object encapsulates a method so that it can be called anonymously.

An event is a mechanism by which a client class can pass in delegates to methods that need to be invoked whenever something happens. When something does happen, the delegate(s) given to the method by its clients are invoked.

To declare an event in C#, we could use the syntax like the following:

 public delegate void SomeDelegate(string sender); public event SomeDelegate SomeEvent; 

After an event has been declared, it must be associated with one or more event handlers before it can be raised. An event handler is nothing more than a method that is called using a delegate. You could use the += operator to associate an event with an instance of a delegate that already exists.

Let's look at another example:

 someClass.SomeEvent += new SomeDelegate(someClass.SomeEventHandler); 

You can also associate an event with a set of handlers (instances of a delegate). You can do so like this:

 someClass.SomeEvent += new SomeDelegate(someClass.SomeEventHandler); someClass.SomeEvent += new SomeDelegate(anotherClass.SomeEventHandler); 

In the preceding example, the SomeEvent event of SomeClass has two handlers. One of them is located in SomeClass and the other is in AnotherClass. You can also assign some static method as a handler to an event.

An event handler can also be detached, as follows:

 someClass.SomeEvent -= new SomeDelegate(someClass.SomeEventHandler); 

In C#, events may be fired by calling them by a name similar to method invocation (for example, SomeEvent ("Some Sender")). The next example will help you to better understand events.

NOTE

Just a few words about compiler behavior related to events processing.

Whenever an event is defined for a class, the compiler generates three methods that are used to manage the underlying delegate:

  • add_<EventName> This is a public method that calls the static Combine method of System.Delegate in order to add another method to its internal invocation list. However, this method is not used explicitly. The same effect can be achieved by using the += operator as specified earlier.

  • remove_<EventName> This is a public method that calls the static Remove method of System.Delegate in order to remove a receiver from the event's invocation list. This method is not called directly. Its job is performed by the -= operator.

  • raise_<EventName> This is a protected method that calls the compiler-generated Invoke method of the delegate in order to call each method in the invocation list.


Let's discuss the following example. It will clarify all potential event-related questions for you:

 using System; namespace Events {         delegate void SomeDelegate (string sender);         interface ISomeInterface {                 event SomeDelegate SomeEvent;                 void PerformSomeEvent ();                 void SomeEventHandler (string sender);         }         abstract class AbstractClass : ISomeInterface {                 public virtual event SomeDelegate SomeEvent;                 public abstract void SomeEventHandler (string sender);                 public abstract void PerformSomeEvent();                 protected void PerformSomeEventInternal (string sender) {                         if (null != SomeEvent) {                                 SomeEvent(sender);                         }                 }                 protected void SomeEventHandlerInternal                  (string sender, string receiver) {                         Console.WriteLine("Some Event has been handled.");                         Console.WriteLine("Sender is: [" + sender + "]");                         Console.WriteLine("Receiver is: [" + receiver + "]");                         Console.WriteLine("--------------------------------");                 }         }         class SomeClass : AbstractClass, ISomeInterface {                 public override void PerformSomeEvent() {                         PerformSomeEventInternal("Some class");                 }                 public override void SomeEventHandler (string sender) {                         SomeEventHandlerInternal (sender, "Some Class");                 }         }         class AnotherClass : AbstractClass, ISomeInterface {                 public override void PerformSomeEvent() {                         PerformSomeEventInternal("Another class");                 }                 public override void SomeEventHandler (string sender) {                         SomeEventHandlerInternal (sender, "Another Class");                 }         }         class SomeApplication {                 public static void SomeEventStaticHandler (string sender) {                         Console.WriteLine("Some Event has been handled.");                         Console.WriteLine("Sender is: [" + sender + "]");                         Console.WriteLine("Receiver is: [Some Application]");                         Console.WriteLine("--------------------------------");                 }                 [STAThread]                 static void Main(string[] args) {                         SomeClass someClass = new SomeClass();                         AnotherClass anotherClass = new AnotherClass();                         someClass.SomeEvent +=   new SomeDelegate(someClass.SomeEventHandler);                         someClass.SomeEvent +=   new SomeDelegate(anotherClass.SomeEventHandler);                         someClass.SomeEvent +=   new SomeDelegate(SomeApplication.SomeEventStaticHandler);                         someClass.PerformSomeEvent();                 }         } } 

Before working with events, we should first define the delegate:

 delegate void SomeDelegate (string sender); 

The second step is a declaration of a class with a public event:

 interface ISomeInterface {         event SomeDelegate SomeEvent;         ...         } abstract class AbstractClass : ISomeInterface {         public virtual event SomeDelegate SomeEvent;         ... } 

The next step is that all event handlers in all classes should be notified of the event. The signature of the event's handler should be the same as the signature of the delegate on which this event depends. In our case, the SomeEvent event is based on the SomeDelegate delegate, so all handlers for some event should have the following signature:

 void SomeHandler (string someStringParameter); 

All handlers should implement custom logic for each class that should be performed when the event is raised.

After performing all the previous steps, you can declare the method in the class where the event is declaredthat will raise the event:

 protected void PerformSomeEventInternal (string sender) {         if (null != SomeEvent) {                 SomeEvent(sender);         } } 

Note that we should compare the event with null before we raise it. Otherwise, if the event doesn't have any handlers assigned to it, the application will cause an exception like the following:

 Unhandled Exception: System.NullReferenceException: Object reference not set to an instance of an object.    at Events.AbstractClass.PerformSomeEventInternal(String sender)   in c:\chapter 10 -   events and delegates\code example\events and delegates\events\main.cs:line 20    at Events.SomeClass.PerformSomeEvent() in c:\chapter 10 -    events and delegates\code example\events and  delegates\events\main.cs:line 33    at Events.SomeApplication.Main(String[] args) in c:\chapter 10 -    events and delegates\code example\events and delegates\events\main.cs:line 67 

After all these steps have been performed, you should assign handlers to the event. You could do so like this:

 someClass.SomeEvent += new SomeDelegate(someClass.SomeEventHandler); someClass.SomeEvent += new SomeDelegate(anotherClass.SomeEventHandler); someClass.SomeEvent += new SomeDelegate(SomeApplication.SomeEventStaticHandler); 

Note that you are allowed to assign static handler methods (SomeApplication.SomeEventStatisHandler) as well as nonstatic methods to an event.

After this, you could raise the event by using the event method mentioned earlier:

 someClass.PerformSomeEvent(); 

The result of running the test application is shown in the following trace:

 Some Event has been handled. Sender is: [Some class] Receiver is: [Some Class] -------------------------------- Some Event has been handled. Sender is: [Some class] Receiver is: [Another Class] -------------------------------- Some Event has been handled. Sender is: [Some class] Receiver is: [Some Application] -------------------------------- 

Three handlers were assigned to the event, so three trace blocks are shown onscreen. Each block shows us the name of raised event, the name of the event's sender, and the name of event's receiver.



    Visual C#. NET 2003 Unleashed
    Visual C#. NET 2003 Unleashed
    ISBN: 672326760
    EAN: N/A
    Year: 2003
    Pages: 316

    flylib.com © 2008-2017.
    If you may any questions please contact us: flylib@qtcs.net